Skip to content

Commit 9566db2

Browse files
committed
Fix thread safety in atexit(f): Lock access to atexit_hooks
- 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
1 parent c6fc12c commit 9566db2

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
@@ -1070,4 +1070,24 @@ end
10701070
end
10711071
end
10721072

1073+
# issue #49746, thread safety in `atexit(f)`
1074+
@testset "atexit thread safety" begin
1075+
f = () -> nothing
1076+
before_len = length(Base.atexit_hooks)
1077+
@sync begin
1078+
for _ in 1:1_000_000
1079+
Threads.@spawn begin
1080+
atexit(f)
1081+
end
1082+
end
1083+
end
1084+
@test length(Base.atexit_hooks) == before_len + 1_000_000
1085+
@test all(hook -> hook === f, Base.atexit_hooks[1 : 1_000_000])
1086+
1087+
# cleanup
1088+
Base.@lock Base._atexit_hooks_lock begin
1089+
deleteat!(Base.atexit_hooks, 1:1_000_000)
1090+
end
1091+
end
1092+
10731093
end # main testset

0 commit comments

Comments
 (0)