Precompile the test environment inside the serialized window - #66
Merged
Conversation
The controller nominates one test process to activate the environment while the others wait, and releases them when it reports back. The point is to build the test environment's precompile caches exactly once, because Julia has no cache file locking before 1.10: two processes precompiling the same package race, and on Windows the loser cannot replace a `.ji` the winner holds open, so it dies with "Cannot write cache file". From Julia 1.9 on that works, because `TestEnv.activate` finishes with `Pkg._auto_precompile` on the sandbox environment. The older TestEnv variants stop at `Pkg.activate`, and `Pkg.instantiate` did not precompile before Julia 1.6 either — so on Julia 1.0 to 1.8 activation built nothing, every peer was released, and each one then evaluated `using <Package>` at the same time. Whoever won decided whether the run passed. Precompiling here puts that work back inside the window the gate already serializes. `Pkg.API.precompile()` rather than `Pkg.precompile()`: the latter does not exist before Julia 1.4, where the name resolves to `Base.precompile` through the implicit `using Base` and throws a MethodError. Verified with a fixture whose only test item has `default_imports=false`, so nothing in the item loads the package: on Julia 1.0 the package and its dependency have no cache file after a run today, and both do with this change.
davidanthoff
added a commit
to julia-vscode/DebugAdapter.jl
that referenced
this pull request
Aug 19, 2026
The Windows + Julia 1.0 failure was not this package's to fix. TestItemControllers nominates one test process to activate the environment and holds the others back until it reports, so that the test environment is precompiled exactly once — but on Julia 1.0 to 1.8 activation built nothing, because those TestEnv variants stop at `Pkg.activate` and only 1.9 and later finish with `Pkg._auto_precompile`. Every process therefore reached its first `using` at the same time, with no cache file locking in Base before 1.10 to protect them. julia-testitems/TestItemControllers.jl#66 closes that, so capping the workers here is unnecessary.
This was referenced Aug 19, 2026
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.
The controller nominates one test process to activate the environment and parks every other one in
ProcessWaitingForPrecompileuntil it reports back. The point of that gate is to build the test environment's precompile caches exactly once, because Julia has no cache file locking before 1.10 (mkpidlock_hookshows up inloading.jlin 1.10 and is absent in 1.9) — two processes precompiling the same package race, and on Windows the loser cannot replace a.jithe winner holds open, so it dies withCannot write cache file.The gap
Whether that window actually builds anything turns out to depend entirely on the vendored TestEnv shim:
julia-1.9,-1.11,-1.12,-1.13Pkg._auto_precompile(temp_ctx; already_instantiated=true), under the comment "Now that we have set up the sandbox environment, precompile all its packages"julia-1.0…julia-1.8activateends atPkg.activate(outer_tmp), and Julia 1.0'sPkg.instantiateends atOperations.build_versionsSo on Julia 1.9+ the gate does what it was built to do. On Julia 1.0–1.8 the nominated process finished activation without writing a single
.ji, all peers were released, and each one then evaluatedusing <Package>at the same time — whoever won the race decided whether the run passed.This surfaced in julia-vscode/DebugAdapter.jl#122: that package had exactly one test item, so one process started and nothing raced. Adding two more made three start together, all loading
JuliaInterpreter, and Windows + Julia 1.0.5 failed withCannot write cache file. The stack put it inrun_testitem, not in activation, which is the tell.The fix
One version-guarded call, inside the window the gate already serializes.
Pkg.API.precompile()rather thanPkg.precompile()— the latter does not exist before Julia 1.4, and before that the name resolves toBase.precompilethrough the implicitusing Baseand throwsMethodError: no method matching precompile(). I hit exactly that on the first attempt.A failure there is swallowed to
@debugon purpose: whatever is wrong comes back with a much better message when a test item loads the package, and it should not turn into a failed activation.Verification
A race is a bad thing to verify by "CI went green", so I verified the mechanism instead, with a fixture whose only test item is
default_imports=false— nothing in the item ever loads the package or its dependency, so whether their cache files exist after a run says exactly one thing: whether activation built them.On Julia 1.0, with
~/.julia/compiled/v1.0cleared each time:Also on Julia 1.0: DebugAdapter's three test items now run clean from a cold cache, and TIC's own suite is 201/201 on 1.12 — including the
test_julia_versions.jlitems, which drive real Julia 1.0/1.1/1.5/1.9/1.10 test processes through this code path.Supersedes the workaround I first proposed on DebugAdapter #122 (capping its CI to one worker), which treated the symptom.
🤖 Generated with Claude Code