Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop running finalizers on exit #51466

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]).
kpamnany marked this conversation as resolved.
Show resolved Hide resolved

Command-line option changes
---------------------------
Expand Down
4 changes: 4 additions & 0 deletions base/gcutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions base/initdefs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion doc/src/devdocs/eval.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions doc/src/devdocs/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
kpamnany marked this conversation as resolved.
Show resolved Hide resolved

!!! compat "Julia 1.11"

Note that as of Julia 1.11, finalizers are no longer run unconditionally at exit.

## `julia_save()`

Expand Down
2 changes: 0 additions & 2 deletions doc/src/manual/embedding.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 0 additions & 4 deletions src/ccalltest.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
36 changes: 0 additions & 36 deletions src/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 0 additions & 3 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
1 change: 0 additions & 1 deletion src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
32 changes: 0 additions & 32 deletions test/atexit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 0 additions & 5 deletions test/ccall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
7 changes: 0 additions & 7 deletions test/spawn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down