Skip to content

Commit e393da4

Browse files
NHDalykpamnany
authored andcommitted
Fix thread safety in atexit(f): Lock access to atexit_hooks (#49774)
- atexit(f) mutates global shared state. - atexit(f) can be called anytime by any thread. - Accesses & mutations to global shared state must be locked if they can be accessed from multiple threads. Add unit test for thread safety of adding many atexit functions in parallel (cherry picked from commit 9dd3090)
1 parent af29371 commit e393da4

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

base/initdefs.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ end
353353
const atexit_hooks = Callable[
354354
() -> Filesystem.temp_cleanup_purge(force=true)
355355
]
356+
const _atexit_hooks_lock = ReentrantLock()
356357

357358
"""
358359
atexit(f)
@@ -374,7 +375,7 @@ calls `exit(n)`, then Julia will exit with the exit code corresponding to the
374375
last called exit hook that calls `exit(n)`. (Because exit hooks are called in
375376
LIFO order, "last called" is equivalent to "first registered".)
376377
"""
377-
atexit(f::Function) = (pushfirst!(atexit_hooks, f); nothing)
378+
atexit(f::Function) = Base.@lock _atexit_hooks_lock (pushfirst!(atexit_hooks, f); nothing)
378379

379380
function _atexit(exitcode::Cint)
380381
while !isempty(atexit_hooks)

test/threads_exec.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,3 +1067,23 @@ end
10671067
popfirst!(LOAD_PATH)
10681068
end
10691069
end
1070+
1071+
# issue #49746, thread safety in `atexit(f)`
1072+
@testset "atexit thread safety" begin
1073+
f = () -> nothing
1074+
before_len = length(Base.atexit_hooks)
1075+
@sync begin
1076+
for _ in 1:1_000_000
1077+
Threads.@spawn begin
1078+
atexit(f)
1079+
end
1080+
end
1081+
end
1082+
@test length(Base.atexit_hooks) == before_len + 1_000_000
1083+
@test all(hook -> hook === f, Base.atexit_hooks[1 : 1_000_000])
1084+
1085+
# cleanup
1086+
Base.@lock Base._atexit_hooks_lock begin
1087+
deleteat!(Base.atexit_hooks, 1:1_000_000)
1088+
end
1089+
end

0 commit comments

Comments
 (0)