persistent-tasks: race-tolerant retry in run_qa (fix flaky Aqua failure fleet-wide)#32
Merged
ChrisRackauckas merged 1 commit intoJul 23, 2026
Conversation
…lure fleet-wide) `run_qa` now runs the persistent-tasks check through a race-tolerant retry (`_has_persistent_tasks_with_retry`) instead of Aqua's single-shot `Aqua.test_persistent_tasks` (disabled in the `Aqua.test_all` call). Aqua's probe cold-precompiles a throwaway wrapper of the package in a subprocess; on a large dependency graph (top-of-stack SciML packages such as SciMLSensitivity) that parallel precompile intermittently trips Julia's "<module> is missing from the cache … may be precompilable after restarting julia" race, so the child exits before loading the wrapper and Aqua misreports it as a persistent task (SciML/SciMLSensitivity#1554, JuliaTesting/Aqua.jl#315). The retry re-runs the wrapper precompile only when it exits without writing its `done.log` sentinel; precompilation stays PARALLEL, so it recompiles only the few modules the race left uncached and the probe stays fast. (Forcing serial precompile was rejected: it dodges the race but writes `done.log` ~837s before a cold serial tree finishes, tripping Aqua's `tmax`.) A genuine persistent task still loads and then holds the process open past `tmax`, which is still detected, so the retry cannot mask a real task. Runs inside `with_clean_persistent_tasks_sources` (so its `Pkg.develop` still gets broken-`[sources]` stripping) and is skipped when persistent-tasks is opted out (`:persistent_tasks in aqua_broken`, or `persistent_tasks = false` in `aqua_kwargs`). Repos can then drop their local workarounds and call `run_qa` plainly. Verified: SciMLTesting's own suite passes (345/345); externally, cold-cache `run_qa` persistent-tasks passes 3/3 with the retry firing on the race (vs the single-shot failing cold, and serial tripping tmax). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GELZcVdiD2HnvtqGs6FTvD
ChrisRackauckas
marked this pull request as ready for review
July 23, 2026 20:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ignore until reviewed by @ChrisRackauckas. Draft, opened by an agent.
The durable, fleet-wide fix for the flaky Aqua persistent-tasks QA failure (SciML/SciMLSensitivity#1554), so repos stop needing per-repo workarounds.
Root cause
run_qa's persistent-tasks check (viaAqua.test_all→Aqua.test_persistent_tasks) cold-precompiles a throwaway wrapper of the package in a subprocess. On a large top-of-stack dependency graph (e.g. SciMLSensitivity), that parallel precompile intermittently trips Julia's<module> is missing from the cache … may be precompilable after restarting juliarace; the child exits before loading the wrapper, so itsdone.logsentinel is never written and Aqua misreports it as a persistent task. The known[sources]-stripping workaround (with_clean_persistent_tasks_sources) does not cover this — it's a precompile race, not a[sources]error.Fix
run_qanow disables Aqua's single-shotpersistent_taskssub-check and runs a race-tolerant_has_persistent_tasks_with_retryinstead: it re-runs the wrapper precompile only when the child exits without writingdone.log. Precompilation stays parallel (fast); the retry recompiles just the few modules the race left uncached.Serial precompile was tried and rejected — it removes the race but is ~6.6× slower, writing
done.log~837s before a cold serial tree finishes and tripping Aqua'stmax=10s(verified 3/3 cold failures). Retry is the only approach that passes cold without slowing the probe.The genuine check is unchanged: a real persistent task loads fine and then holds the process open past
tmax, still detected — the retry only fires on the missing-done.lograce, unreachable for a real task. Runs insidewith_clean_persistent_tasks_sources(keeps broken-[sources]stripping) and is skipped when persistent-tasks is opted out (:persistent_tasks in aqua_broken, orpersistent_tasks = falseinaqua_kwargs).Verification (Julia 1.12.6)
run_qatests exercise the retry on the warm/happy path).with_clean_persistent_tasks_sources(() -> @test !_has_persistent_tasks_with_retry(SciMLSensitivity)),JULIA_NUM_PRECOMPILE_TASKS=4, wiped cache): 3/3 pass, the race fired each time (Accessors … missing from the cache), the retry logged + recovered,done.logwritten on the warm retry,JULIA_NUM_PRECOMPILE_TASKSunchanged (no leak), active project restored. The unpatched single-shot fails cold; serial tripstmax.Runic-clean.
Follow-up
Once released: SciML/SciMLSensitivity's
test/QA/aqua.jlreverts to a plainrun_qa(SciMLSensitivity; …)(dropping its local retry) — see the companion SciMLSensitivity PR. Other repos with local persistent-tasks workarounds can do the same. The truly-upstream fix belongs in Aqua itself (race-tolerant persistent-tasks precompile, JuliaTesting/Aqua.jl#315); this is the SciML-fleet fix until that lands.🤖 Generated with Claude Code