Retry ports tree synchronisation on transient mirror failures - #4
jolierabideau wants to merge 3 commits into
Conversation
The default ports tree source, rsync://rsync.macports.org, is served by
a rotating pool of volunteer mirrors. When a runner draws an
unresponsive mirror, `port sync` hangs until the connection drops and
then fails with
Error: Synchronization of the local ports tree failed doing rsync
A healthy synchronisation takes about 13 seconds, so the failure is a
stalled mirror rather than a configuration problem, and it clears on
its own. Consumers of this action were working around it with their own
`port sync` step, which had the same single-attempt behaviour.
Synchronise the ports tree as part of the installation instead, and
retry it a few times with an increasing delay. Set connection and
transfer timeouts in `rsync_options` so a stalled mirror fails promptly
rather than holding the step open, which is what makes retrying
worthwhile. Run the last attempt with debug output so a persistent
failure leaves something to triage in the workflow log.
The rsync_options default is carried over from macports.conf rather
than rewritten, so `-rtzvl --delete-after` is preserved and only the
timeouts are added.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The --contimeout option was added in rsync 3.1.0. macOS 14 runners ship
rsync 2.6.9, which rejects it outright, so configuring it made every
synchronisation attempt fail with
rsync: --contimeout=15: unknown option
rsync error: syntax or usage error (code 1) [client=2.6.9]
Recent macOS versions ship openrsync, which does accept it, so this
could not be caught on a single runner. Keep --timeout, which all of
them support and which covers the stalled transfer this is meant to
guard against; a connection that is never established fails through
the TCP timeout anyway.
Cover this with a test that runs the configured options through the
rsync the runner actually provides, so an unsupported option is caught
by the testsuite rather than by an installation four minutes later.
The implementations word the rejection differently: rsync 2.6.9 says
"unknown option" while openrsync says "unrecognized option".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI is red for a pre-existing reason, not because of this PRThe What fails is a later, unrelated step: This fails on unmodified Cause (recorded here since repository issues are disabled)The MacPorts The assertion only ever passed because the pin happened to equal the latest release — true from 2026-04-23 until 2026-08-25. The last CI run on The broader point: the documented Follow-up options, for a separate PR
Also worth notingThe postflight's selfupdate failure is explicitly non-fatal — it prints a message and the install still reports success. So before this PR the ports tree sync was silently best-effort, which is why consumers such as |
|
Restacked onto #5 so CI runs with both fixes present. The two PRs were blocking each other: this one failed on the stale Base is now |
Problem
macOS CI jobs in
paranext-corehave been failing at theUpdate MacPorts ports treestep:The default ports tree source,
rsync://rsync.macports.org/macports/release/tarballs/ports.tar, is served by a rotating pool of volunteer mirrors. When a runner draws an unresponsive one, rsync hangs until the connection drops and then fails.Evidence that this is a stalled mirror and not a configuration problem:
3447565717034478788344344803854323448376492134483979256A healthy sync takes ~13 seconds; the failures burned 75 seconds each, which is a stall rather than a rejection. The mirror answered normally from outside CI throughout.
Change
Synchronise the ports tree as part of the installation, with retries:
sync_portsinsubr/macports.sh— up to 4 attempts, delays increasing 10s / 20s / 30s. The last attempt runsport -d syncso a persistent failure leaves debug output in the log without paying for an extra 142 MB download.write_rsync_options— setsrsync_optionsinmacports.confwith--timeout=60 --contimeout=15. This is what makes retrying worthwhile; without it, 4 attempts against a stalled mirror would be a 5-minute step. The MacPorts default flags-rtzvl --delete-afterare carried over, so only the timeouts are added.install_macportsafter the install (which is whenmacports.conffirst exists) and beforeinstall_ports, which needs a populated tree anyway.Consumers no longer need their own
port syncstep;paranext-coregets a follow-up PR to drop its step and bump the action ref.Tests
New
testsuite/modules/macports, covering the retry loop with a stubbedportandsleepso it needs no network:rsync_optionsis written when absent, keeps the MacPorts defaults, and replaces any existing setting without disturbing the rest of the fileEach test was watched failing before the implementation existed, and mutation-checked afterwards — forcing
attempts=1, dropping the timeouts, and flattening the backoff each kill exactly the tests they should.write_rsync_optionswas also run against the realmacports.conffrommacports-baseto confirm it removes the commented#rsync_optionsline, appends ours, and leavesrsync_server/rsync_diruntouched.Notes
/opt/localwith permission fixups would not pay for itself.paranext-corepins MacPorts 2.11.5 while this repo defaults to 2.12.5, but the only sync-related change in 2.12.x is a tarball extraction optimisation. The rsync client is macOS's, and the failure is mirror-side.🤖 Generated with Claude Code
This change is