From f416923e952b8230d9033dafc11bfc502b748cf6 Mon Sep 17 00:00:00 2001 From: Nathan Daly Date: Mon, 15 May 2023 08:09:02 -0600 Subject: [PATCH] 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 --- base/initdefs.jl | 3 ++- test/threads_exec.jl | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/base/initdefs.jl b/base/initdefs.jl index 89ebecaefbdc4..002984b83dd97 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -353,6 +353,7 @@ end const atexit_hooks = Callable[ () -> Filesystem.temp_cleanup_purge(force=true) ] +const _atexit_hooks_lock = ReentrantLock() """ atexit(f) @@ -374,7 +375,7 @@ calls `exit(n)`, then Julia will exit with the exit code corresponding to the last called exit hook that calls `exit(n)`. (Because exit hooks are called in LIFO order, "last called" is equivalent to "first registered".) """ -atexit(f::Function) = (pushfirst!(atexit_hooks, f); nothing) +atexit(f::Function) = Base.@lock _atexit_hooks_lock (pushfirst!(atexit_hooks, f); nothing) function _atexit(exitcode::Cint) while !isempty(atexit_hooks) diff --git a/test/threads_exec.jl b/test/threads_exec.jl index 68ba9377cf955..a6c52484f023b 100644 --- a/test/threads_exec.jl +++ b/test/threads_exec.jl @@ -1067,3 +1067,23 @@ end popfirst!(LOAD_PATH) end end + +# issue #49746, thread safety in `atexit(f)` +@testset "atexit thread safety" begin + f = () -> nothing + before_len = length(Base.atexit_hooks) + @sync begin + for _ in 1:1_000_000 + Threads.@spawn begin + atexit(f) + end + end + end + @test length(Base.atexit_hooks) == before_len + 1_000_000 + @test all(hook -> hook === f, Base.atexit_hooks[1 : 1_000_000]) + + # cleanup + Base.@lock Base._atexit_hooks_lock begin + deleteat!(Base.atexit_hooks, 1:1_000_000) + end +end