Open SteveBeeblebrox opened 2 months ago
since the documentation for std::process::exit() makes a point of saying no destructors will be called and the program will exit immediately
To be clear, the documentation says this:
Note that because this function never returns, and that it terminates the process, no destructors on the current stack or any other thread’s stack will be run
The "on the current stack" part is important. atexit
handlers are not on the stack and are invoked by the platform's exit
routine (or equivalent), not by Rust.
please feel free to send a PR improving the documentation!
this relates to #126600, where it was clearly decided to have this function be C exit()
and nothing more or less.
Seems like behavior matches documentation -- thread-local variables are not on the stack. What more could the docs say?
Seems like behavior matches documentation -- thread-local variables are not on the stack. What more could the docs say?
Would definitely be useful to know[^1] that atexit
and on_exit
handlers will be executed and that something that already makes use of them(as per the above comment) is the thread local vars and thus their destructors will be executed which means any drop()
of a thread local static will execute.
But I'm pretty bad at expressing myself, else I would've made a PR to update the doc for it myself.
related: https://github.com/rust-lang/rust/issues/110897
Actually nevermind the following isn't right: and it only works due to the _exit(0) at the end. Ugly workaround which doesn't let TLS destructors run because it immediately exits(playground):
#![feature(thread_local)]
use libc::{_exit, atexit};
use std::sync::LazyLock;
#[derive(Debug)]
struct Droppable {
name: &'static str,
}
impl Drop for Droppable {
fn drop(&mut self) {
println!("Dropping {}", self.name);
println!("{}", std::backtrace::Backtrace::force_capture());
}
}
impl Droppable {
fn new(name: &'static str) -> Self {
Self { name }
}
}
static SHARED_STATIC: LazyLock<Droppable> = LazyLock::new(|| Droppable::new("SHARED_STATIC"));
thread_local! {
static TLS_STATIC: Droppable = Droppable::new("TLS_STATIC");
}
#[thread_local]
static UNSTABLE_THREAD_LOCAL: LazyLock<Droppable> =
LazyLock::new(|| Droppable::new("UNSTABLE_THREAD_LOCAL"));
extern "C" fn exit_handler() {
println!("Calling exit handler");
unsafe {
_exit(1); // The _exit function terminates the process immediately without calling any destructors.
}
}
fn main() {
// Register the exit handler
unsafe {
atexit(exit_handler as extern "C" fn());
}
// Force initialize
LazyLock::force(&SHARED_STATIC);
LazyLock::force(&UNSTABLE_THREAD_LOCAL);
TLS_STATIC.with(|_| {});
println!("Main function end");
// The program will terminate here using _exit, bypassing destructors
unsafe {
_exit(0);
}
}
[^1]: because it may not even cross some people's minds, like me
Thread locals do not use atexit
handlers, they use yet another handler mechanism.
I don't know if we guarantee that the current thread's thread-locals will be dropped on exit
; this might differ from platform to platform.
Unlike normal static variables (or the differently implemented unstable
#[thread_local]
statics),thread_local!
/LocalKey
statics are still dropped after callingstd::process::exit()
.I tried this code:
Link to playground
I expected to see this happen: No output from
drop()
since the documentation forstd::process::exit()
makes a point of saying no destructors will be called and the program will exit immediately. When calling exit, I assumed that almost nothing happens after that.Instead, this happened: The static variable wrapped with
thread_local!
had its drop implementation called after callingstd::process::exit()
meanwhile a normal static variable and one with the unstable#[thread_local]
attribute did not.Other notes: The same behavior occurs where only the
thread_local!
value is dropped when normally exiting frommain()
.LocalKey's documentation sort of makes it sound like this is intentional behavior that the destructors are run even when exiting the main thread (See "Platform-specific behavior" 1.). If this is working as intended, it would be nice if the documentation were slightly clearer.
Meta
rustc --version --verbose
:Backtrace
``` 0:::drop
at ./src/main.rs:12:24
1: core::ptr::drop_in_place
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/core/src/ptr/mod.rs:542:1
2: core::ptr::drop_in_place>
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/core/src/ptr/mod.rs:542:1
3: core::mem::drop
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/core/src/mem/mod.rs:938:24
4: std::sys::thread_local::native::lazy::destroy::{{closure}}
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/sys/thread_local/native/lazy.rs:99:9
5: std::sys::thread_local::abort_on_dtor_unwind
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/sys/thread_local/mod.rs:168:5
6: std::sys::thread_local::native::lazy::destroy
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/sys/thread_local/native/lazy.rs:94:5
7: __call_tls_dtors
8:
9: exit
10: std::sys::pal::unix::os::exit
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/sys/pal/unix/os.rs:761:14
11: std::process::exit
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/process.rs:2320:5
12: playground::main
at ./src/main.rs:40:5
13: core::ops::function::FnOnce::call_once
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/core/src/ops/function.rs:250:5
14: std::sys::backtrace::__rust_begin_short_backtrace
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/sys/backtrace.rs:155:18
15: std::rt::lang_start::{{closure}}
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/rt.rs:159:18
16: core::ops::function::impls:: for &F>::call_once
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/core/src/ops/function.rs:284:13
17: std::panicking::try::do_call
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/panicking.rs:553:40
18: std::panicking::try
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/panicking.rs:517:19
19: std::panic::catch_unwind
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/panic.rs:350:14
20: std::rt::lang_start_internal::{{closure}}
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/rt.rs:141:48
21: std::panicking::try::do_call
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/panicking.rs:553:40
22: std::panicking::try
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/panicking.rs:517:19
23: std::panic::catch_unwind
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/panic.rs:350:14
24: std::rt::lang_start_internal
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/rt.rs:141:20
25: std::rt::lang_start
at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/std/src/rt.rs:158:17
26: main
27: __libc_start_main
28: _start
```