@@ -354,6 +354,7 @@ const atexit_hooks = Callable[
354354 () -> Filesystem. temp_cleanup_purge (force= true )
355355]
356356const _atexit_hooks_lock = ReentrantLock ()
357+ global _atexit_hooks_finished:: Bool = false
357358
358359"""
359360 atexit(f)
@@ -374,12 +375,40 @@ exit code `n` (instead of the original exit code). If more than one exit hook
374375calls `exit(n)`, then Julia will exit with the exit code corresponding to the
375376last called exit hook that calls `exit(n)`. (Because exit hooks are called in
376377LIFO order, "last called" is equivalent to "first registered".)
378+
379+ Note: Once all exit hooks have been called, no more exit hooks can be registered,
380+ and any call to `atexit(f)` after all hooks have completed will throw an exception.
381+ This situation may occur if you are registering exit hooks from background Tasks that
382+ may still be executing concurrently during shutdown.
377383"""
378- atexit (f:: Function ) = Base. @lock _atexit_hooks_lock (pushfirst! (atexit_hooks, f); nothing )
384+ function atexit (f:: Function )
385+ Base. @lock _atexit_hooks_lock begin
386+ _atexit_hooks_finished && error (" cannot register new atexit hook; already exiting." )
387+ pushfirst! (atexit_hooks, f)
388+ return nothing
389+ end
390+ end
379391
380392function _atexit (exitcode:: Cint )
381- while ! isempty (atexit_hooks)
382- f = popfirst! (atexit_hooks)
393+ # Don't hold the lock around the iteration, just in case any other thread executing in
394+ # parallel tries to register a new atexit hook while this is running. We don't want to
395+ # block that thread from proceeding, and we can allow it to register its hook which we
396+ # will immediately run here.
397+ while true
398+ local f
399+ Base. @lock _atexit_hooks_lock begin
400+ # If this is the last iteration, atomically disable atexit hooks to prevent
401+ # someone from registering a hook that will never be run.
402+ # (We do this inside the loop, so that it is atomic: no one can have registered
403+ # a hook that never gets run, and we run all the hooks we know about until
404+ # the vector is empty.)
405+ if isempty (atexit_hooks)
406+ global _atexit_hooks_finished = true
407+ break
408+ end
409+
410+ f = popfirst! (atexit_hooks)
411+ end
383412 try
384413 if hasmethod (f, (Cint,))
385414 f (exitcode)
0 commit comments