Re-land race-tolerant persistent-tasks retry in run_qa - #42
Re-land race-tolerant persistent-tasks retry in run_qa#42ChrisRackauckas-Claude wants to merge 1 commit into
Conversation
Reverts 6da14e3, which reverted #32 twelve minutes after it merged with no stated reason. Aqua's `test_persistent_tasks` cold-precompiles a throwaway wrapper package in a subprocess. On a large dependency graph that parallel precompile can exit without writing its `done.log` sentinel, and Aqua reports the package as holding a persistent task. `run_qa` disables Aqua's single-shot check and runs `_has_persistent_tasks_with_retry`, which re-runs the wrapper only on the missing-sentinel path; the retry hits a now-warm cache. A genuine persistent task writes `done.log` and then holds the process past `tmax`, which is still detected, so the retry cannot mask a real task. Resolved against the `_standard_aqua_kwargs` helper added since the revert: the retry's `persistent_tasks = false` now merges over the standard kwargs rather than over raw `aqua_kwargs`, so the piracy `treat_as_own` defaults from #37 and #38 are preserved. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJbtE2oppQxPFNGSJbhm22
|
Confirmed the root cause on a real runner. I added a temporary diagnostic to SciML/ComplementaritySolve.jl#76 that reproduces Aqua's probe with stderr passed through rather than sent to This is exactly the race #32 described. Exit code 0 and no termination signal: not a crash, not an OOM, not a real persistent task. The same run demonstrates the remedy incidentally — the diagnostic warmed the cache, so Aqua's own probe immediately afterwards passed, 20/20. That is precisely what the retry in this PR automates, and it matches Julia's own guidance in the warning text ( Alternative causes ruled out locally before reaching for the retry: no persistent task ( |
Please ignore until reviewed by @ChrisRackauckas. Draft, opened by an agent.
Re-lands #32, which was reverted by #33 twelve minutes after merging, with no stated reason and no review comments. Re-landing was explicitly requested.
Why
Aqua's
test_persistent_taskscold-precompiles a throwaway wrapper package in a subprocess and waits for it to write adone.logsentinel. On a large dependency graph that parallel precompile can exit without writing the sentinel, and Aqua then reports the package as holding a persistent task. The failure is indistinguishable from a real one because Aqua callsPkg.precompile(; io = devnull), so nothing reaches stderr:run_qadisables Aqua's single-shot sub-check and runs_has_persistent_tasks_with_retry, which re-runs the wrapper only on the missing-sentinel path. Precompilation stays parallel, so the retry recompiles just the few modules the first attempt left uncached. A genuine persistent task writesdone.logand then holds the process open pasttmax, which is still detected — the retry fires only on a path a real task cannot reach.Evidence this fixes a live failure
SciML/ComplementaritySolve.jl#76 is currently red on exactly this. Its 2026-07-26 run, on SciMLTesting 2.4.1 (retry present), shows the retry firing and recovering:
The same repo on 2.6.1 (retry absent) fails the check on every run. I could not reproduce the underlying precompile death locally — a cold depot on Julia 1.12 precompiles all 230 dependencies and exits 0 — which is consistent with it being a resource/race condition on the 4-core GitHub runner rather than anything in the package.
Conflict resolution
The revert did not apply cleanly;
_standard_aqua_kwargswas added since (#37, #38). Resolved so the retry'spersistent_tasks = falsemerges over_standard_aqua_kwargs(aqua_kwargs)rather than over rawaqua_kwargs, preserving the piracytreat_as_owndefaults:Validation
Pkg.test()on this branch: 344 pass, 0 fail, exit 0 (Julia 1.12.6)._has_persistent_tasks_with_retry(ComplementaritySolve)against the PR-76 checkout: passes,retry probe | 1 pass | 21.3s.--checkonsrc/SciMLTesting.jl: exit 0.The truly-upstream fix still belongs in Aqua (JuliaTesting/Aqua.jl#315) — at minimum it should stop swallowing the subprocess error, since right now every failure mode looks identical.