From 3fbc40ab2aed5e60dca3b57b28864680314623ee Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Tue, 26 Sep 2023 11:57:51 -0400 Subject: [PATCH 1/7] Do not run finalizers on exit This has caused a number of problems like freezes on exit. Other language runtimes (Java, .NET, Go) have also made the decision to no longer run finalizers on exit. Remove call to `jl_gc_run_all_finalizers()` and the function itself. --- src/gc.c | 36 ------------------------------------ src/init.c | 3 --- src/julia_internal.h | 1 - 3 files changed, 40 deletions(-) diff --git a/src/gc.c b/src/gc.c index 42a9daa01a747..43ef83dcea08a 100644 --- a/src/gc.c +++ b/src/gc.c @@ -527,42 +527,6 @@ JL_DLLEXPORT int8_t jl_gc_is_in_finalizer(void) return jl_current_task->ptls->in_finalizer; } -static void schedule_all_finalizers(arraylist_t *flist) JL_NOTSAFEPOINT -{ - void **items = flist->items; - size_t len = flist->len; - for(size_t i = 0; i < len; i+=2) { - void *v = items[i]; - void *f = items[i + 1]; - if (__unlikely(!v)) - continue; - schedule_finalization(v, f); - } - flist->len = 0; -} - -void jl_gc_run_all_finalizers(jl_task_t *ct) -{ - int gc_n_threads; - jl_ptls_t* gc_all_tls_states; - gc_n_threads = jl_atomic_load_acquire(&jl_n_threads); - gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states); - // this is called from `jl_atexit_hook`; threads could still be running - // so we have to guard the finalizers' lists - JL_LOCK_NOGC(&finalizers_lock); - schedule_all_finalizers(&finalizer_list_marked); - for (int i = 0; i < gc_n_threads; i++) { - jl_ptls_t ptls2 = gc_all_tls_states[i]; - if (ptls2 != NULL) - schedule_all_finalizers(&ptls2->finalizers); - } - // unlock here because `run_finalizers` locks this - JL_UNLOCK_NOGC(&finalizers_lock); - gc_n_threads = 0; - gc_all_tls_states = NULL; - run_finalizers(ct); -} - void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT { assert(jl_atomic_load_relaxed(&ptls->gc_state) == 0); diff --git a/src/init.c b/src/init.c index a046b4e6dcb21..8f0180a9f9d75 100644 --- a/src/init.c +++ b/src/init.c @@ -295,9 +295,6 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) JL_NOTSAFEPOINT_ENTER JL_STDOUT = (uv_stream_t*) STDOUT_FILENO; JL_STDERR = (uv_stream_t*) STDERR_FILENO; - if (ct) - jl_gc_run_all_finalizers(ct); - uv_loop_t *loop = jl_global_event_loop(); if (loop != NULL) { struct uv_shutdown_queue queue = {NULL, NULL}; diff --git a/src/julia_internal.h b/src/julia_internal.h index 7883844d908f8..4672995d7c0de 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -572,7 +572,6 @@ JL_DLLEXPORT int64_t jl_gc_diff_total_bytes(void) JL_NOTSAFEPOINT; JL_DLLEXPORT int64_t jl_gc_sync_total_bytes(int64_t offset) JL_NOTSAFEPOINT; void jl_gc_track_malloced_array(jl_ptls_t ptls, jl_array_t *a) JL_NOTSAFEPOINT; void jl_gc_count_allocd(size_t sz) JL_NOTSAFEPOINT; -void jl_gc_run_all_finalizers(jl_task_t *ct); void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task); void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT; From 43dfdb98a9bbe09c8d62a05a26a7470b5b5068cb Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Mon, 9 Oct 2023 17:21:09 -0400 Subject: [PATCH 2/7] Drop test that relies on finalizers running at exit The test verifies that `atexit`s cannot be registered once Julia has started exiting and has run the already-registered `atexit`s. --- test/atexit.jl | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/test/atexit.jl b/test/atexit.jl index 64b56e32466df..bae10628167ae 100644 --- a/test/atexit.jl +++ b/test/atexit.jl @@ -212,38 +212,6 @@ using Test exit(0) """ => 11, # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # 3. attempting to register a hook after all hooks have finished (disallowed) - """ - const atexit_has_finished = Threads.Atomic{Bool}(false) - atexit() do - Threads.@spawn begin - # Block until the atexit hooks have all finished. We use a manual "spin - # lock" because task switch is disallowed inside the finalizer, below. - while !atexit_has_finished[] end - try - # By the time this runs, all the atexit hooks will be done. - # So this will throw. - atexit() do - exit(11) - end - catch - # Meaning we _actually_ exit 22. - exit(22) - end - end - end - # Finalizers run after the atexit hooks, so this blocks exit until the spawned - # task above gets a chance to run. - x = [] - finalizer(x) do x - # Allow the spawned task to finish - atexit_has_finished[] = true - # Then spin forever to prevent exit. - while atexit_has_finished[] end - end - exit(0) - """ => 22, - # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ) for julia_expr in keys(julia_expr_list) cmd_eval = _atexit_tests_gen_cmd_eval(julia_expr) From df30ac4bb71a6c61072c20c9369d0b69f64a25f9 Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Mon, 2 Oct 2023 12:04:11 -0400 Subject: [PATCH 3/7] Update docs to remove all references to running finalizers at exit --- base/initdefs.jl | 3 +-- doc/src/devdocs/eval.md | 2 +- doc/src/devdocs/init.md | 7 +++++-- doc/src/manual/embedding.md | 2 -- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/base/initdefs.jl b/base/initdefs.jl index c04a97971eff2..06d762355fcdc 100644 --- a/base/initdefs.jl +++ b/base/initdefs.jl @@ -377,8 +377,7 @@ global _atexit_hooks_finished::Bool = false atexit(f) Register a zero- or one-argument function `f()` to be called at process exit. -`atexit()` hooks are called in last in first out (LIFO) order and run before -object finalizers. +`atexit()` hooks are called in last in first out (LIFO) order. If `f` has a method defined for one integer argument, it will be called as `f(n::Int32)`, where `n` is the current exit code, otherwise it will be called diff --git a/doc/src/devdocs/eval.md b/doc/src/devdocs/eval.md index 8f2fd68159676..79a8230df3c89 100644 --- a/doc/src/devdocs/eval.md +++ b/doc/src/devdocs/eval.md @@ -57,7 +57,7 @@ The 10,000 foot view of the whole process is as follows: method returns. 14. Just before exiting, `main()` calls [`jl_atexit_hook(exit_code)`](https://github.com/JuliaLang/julia/blob/master/src/init.c). This calls `Base._atexit()` (which calls any functions registered to [`atexit()`](@ref) inside - Julia). Then it calls [`jl_gc_run_all_finalizers()`](https://github.com/JuliaLang/julia/blob/master/src/gc.c). + Julia). Finally, it gracefully cleans up all `libuv` handles and waits for them to flush and close. ## [Parsing](@id dev-parsing) diff --git a/doc/src/devdocs/init.md b/doc/src/devdocs/init.md index 1e0e1173f8695..a9b5f9a6f8cbf 100644 --- a/doc/src/devdocs/init.md +++ b/doc/src/devdocs/init.md @@ -218,8 +218,11 @@ the stack now rapidly unwinds back to `main()`. ## `jl_atexit_hook()` `main()` calls [`jl_atexit_hook()`](https://github.com/JuliaLang/julia/blob/master/src/init.c). -This calls `Base._atexit`, then calls [`jl_gc_run_all_finalizers()`](https://github.com/JuliaLang/julia/blob/master/src/gc.c) -and cleans up libuv handles. +This calls `Base._atexit` and cleans up libuv handles. + +!!! compat "Julia 1.11" + + Note that as of Julia 1.11, finalizers are no longer run unconditionally at exit. ## `julia_save()` diff --git a/doc/src/manual/embedding.md b/doc/src/manual/embedding.md index 2b6e48c533849..db527fdf6fb67 100644 --- a/doc/src/manual/embedding.md +++ b/doc/src/manual/embedding.md @@ -32,7 +32,6 @@ int main(int argc, char *argv[]) /* strongly recommended: notify Julia that the program is about to terminate. this allows Julia time to cleanup pending write requests - and run all finalizers */ jl_atexit_hook(0); return 0; @@ -166,7 +165,6 @@ int main(int argc, char *argv[]) /* strongly recommended: notify Julia that the program is about to terminate. this allows Julia time to cleanup pending write requests - and run all finalizers */ jl_atexit_hook(0); return 0; From c81708fd9be3cadeee320d35c27e394b8b73aea8 Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Mon, 2 Oct 2023 12:08:21 -0400 Subject: [PATCH 4/7] Update NEWS --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index c8ed757ef3fd7..01178366a3dd9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,7 @@ Compiler/Runtime improvements * Updated GC heuristics to count allocated pages instead of individual objects ([#50144]). * A new `LazyLibrary` type is exported from `Libdl` for use in building chained lazy library loads, primarily to be used within JLLs ([#50074]). +* Stop running finalizers at exit ([51466]). Command-line option changes --------------------------- From c3f5c3a8c7aeb13001290e0cb1fce69bf510408e Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Wed, 11 Oct 2023 12:32:42 -0400 Subject: [PATCH 5/7] Back-out PR #19911 Since we no longer run finalizers at exit. --- src/ccalltest.c | 4 ---- test/ccall.jl | 5 ----- 2 files changed, 9 deletions(-) diff --git a/src/ccalltest.c b/src/ccalltest.c index 0c7c85b328415..267b281efdcfc 100644 --- a/src/ccalltest.c +++ b/src/ccalltest.c @@ -956,9 +956,5 @@ DLLEXPORT int threadcall_args(int a, int b) { return a + b; } -DLLEXPORT void c_exit_finalizer(void* v) { - printf("c_exit_finalizer: %d, %u", *(int*)v, (unsigned)((uintptr_t)v & (uintptr_t)1)); -} - // global variable for cglobal testing DLLEXPORT const int global_var = 1; diff --git a/test/ccall.jl b/test/ccall.jl index 6e8269a36225d..47a4a1ecc419c 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1102,11 +1102,6 @@ let A = [1] @test ccall((:get_c_int, libccalltest), Cint, ()) == -1 end -# Pointer finalizer at exit (PR #19911) -let result = read(`$(Base.julia_cmd()) --startup-file=no -e "A = Ref{Cint}(42); finalizer(cglobal((:c_exit_finalizer, \"$libccalltest\"), Cvoid), A)"`, String) - @test result == "c_exit_finalizer: 42, 0" -end - # SIMD Registers const VecReg{N,T} = NTuple{N,VecElement{T}} From 5dde0857dc2be42237ac37cebb71216fecd9ca45 Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Wed, 11 Oct 2023 12:48:59 -0400 Subject: [PATCH 6/7] Back out test for #26687 Relies on finalizers running at exit. --- test/spawn.jl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/spawn.jl b/test/spawn.jl index 3fdfa794ff39e..f078269805a4d 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -665,13 +665,6 @@ let s = " \$abc " @test s[Base.shell_parse(s)[2]] == "abc" end -# Logging macros should not output to finalized streams (#26687) -let - cmd = `$exename -e 'finalizer(x->@info(x), "Hello")'` - output = readchomp(pipeline(cmd, stderr=catcmd)) - @test occursin("Info: Hello", output) -end - # Sys.which() testing psep = if Sys.iswindows() ";" else ":" end withenv("PATH" => "$(Sys.BINDIR)$(psep)$(ENV["PATH"])") do From 1add939d17bbd2f84bc8f9befe582c4340e994f7 Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Wed, 11 Oct 2023 14:39:04 -0400 Subject: [PATCH 7/7] Update `finalizer` docstring Add a `compat` note that as of Julia 1.11, finalizers will not be run on exit. --- base/gcutils.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/gcutils.jl b/base/gcutils.jl index fed30befd7d5c..38fffcf08a7e2 100644 --- a/base/gcutils.jl +++ b/base/gcutils.jl @@ -56,6 +56,10 @@ Using the `@async` macro (to defer context switching to outside of the finalizer Note that there is no guaranteed world age for the execution of `f`. It may be called in the world age in which the finalizer was registered or any later world age. +!!! compat "Julia 1.11" + As of Julia 1.11, finalizers are not run on exit. Thus it is not guaranteed that a + finalizer will be called. + # Examples ```julia finalizer(my_mutable_struct) do x