Skip to content

Avoid invalidating Base's condition code with YieldingSpinLock - #35

Merged
maleadt merged 1 commit into
mainfrom
tb/havelock-invalidation
Oct 21, 2026
Merged

maleadt merged 1 commit into
mainfrom
tb/havelock-invalidation

Conversation

@maleadt

@maleadt maleadt commented Oct 21, 2026

Copy link
Copy Markdown
Member

YieldingSpinLock (used by Completion) defined Base.assert_havelock and islocked methods. On Julia 1.13+ that invalidates Base.assert_havelock(::GenericCondition), which Base compiled for conditions with an unknown lock type, plus everything depending on it through wait/notify. One visible consequence: every process loading GPUToolbox (and so GPUArrays, CUDA.jl, oneAPI.jl, ...) recompiles Base.Filesystem.temp_cleanup_atexit at exit.

Julia invalidations before after julia -e 'using GPUToolbox' before after
1.10 11 11 123 ms 122 ms
1.11 30 30 100 ms 101 ms
1.12 10 10 130 ms 131 ms
1.13 269 0 227 ms 121 ms
nightly 488 0 142 ms 143 ms

(Invalidations from SnoopCompile's @snoop_invalidations using GPUToolbox; timings are medians of 30 interleaved runs on a loaded machine. On 1.13, --trace-compile-timing shows temp_cleanup_atexit taking about 100 ms to recompile at exit.)

The fix: on 1.13+, YieldingSpinLock is a Threads.AbstractSpinLock with the owned field that Base's spin lock methods expect, and only overrides lock. That way it reuses Base's trylock, unlock, islocked and assert_havelock, and no new methods reach the poorly-inferred condition code. Earlier versions keep the current wrapper around SpinLock: there is no AbstractSpinLock, and the remaining invalidations are minor and don't cause the exit-time recompilation. The tests now also check that assert_havelock still throws when the lock isn't held.

Defining `Base.assert_havelock` and `islocked` for `YieldingSpinLock` invalidated
the code Base compiled for `GenericCondition`s with an unknown lock type (about 270
method instances on Julia 1.13, 490 on nightly). As a result, every process loading
GPUToolbox recompiled `Base.Filesystem.temp_cleanup_atexit` at exit, which takes
about 100 ms.

On Julia 1.13+, make `YieldingSpinLock` an `AbstractSpinLock` with the `owned` field
Base's methods expect, and only override `lock`. That reuses Base's `trylock`,
`unlock`, `islocked` and `assert_havelock` methods, so loading the package no longer
invalidates anything. Older versions keep the wrapper around `SpinLock`; there, the
invalidations are minor and do not cause the exit-time recompilation.
@maleadt
maleadt merged commit f0c9b0d into main Oct 21, 2026
10 checks passed
@maleadt
maleadt deleted the tb/havelock-invalidation branch October 21, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant