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

LocalKey dropped after calling std::process::exit() #127637

Open
SteveBeeblebrox opened this issue Jul 12, 2024 · 6 comments
Open

LocalKey dropped after calling std::process::exit() #127637

SteveBeeblebrox opened this issue Jul 12, 2024 · 6 comments
Labels
A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools A-process Area: `std::process` and `std::env` C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@SteveBeeblebrox
Copy link

Unlike normal static variables (or the differently implemented unstable #[thread_local] statics), thread_local!/LocalKey statics are still dropped after calling std::process::exit().

I tried this code:

#![feature(thread_local)]
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 {
        return 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"));

fn main() {
    // Force initialize
    LazyLock::force(&SHARED_STATIC);
    LazyLock::force(&UNSTABLE_THREAD_LOCAL);
    TLS_STATIC.with(|_| {});
    
    println!("std::process::exit(1)");
    std::process::exit(1);
}

Link to playground

I expected to see this happen: No output from drop() since the documentation for std::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 calling std::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 from main().

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:

rustc 1.81.0-nightly (20ae37c18 2024-07-07)
binary: rustc
commit-hash: 20ae37c18df95f9246c019b04957d23b4164bf7a
commit-date: 2024-07-07
host: x86_64-unknown-linux-gnu
release: 1.81.0-nightly
LLVM version: 18.1.7
Backtrace

0: <playground::Droppable as core::ops::drop::Drop>::drop
             at ./src/main.rs:12:24
   1: core::ptr::drop_in_place<playground::Droppable>
             at /rustc/5315cbe15b79533f380bbb6685aa5480d5ff4ef5/library/core/src/ptr/mod.rs:542:1
   2: core::ptr::drop_in_place<std::sys::thread_local::native::lazy::State<playground::Droppable,()>>
             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: <unknown>
   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::<impl core::ops::function::FnOnce<A> 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

@SteveBeeblebrox SteveBeeblebrox added the C-bug Category: This is a bug. label Jul 12, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jul 12, 2024
@ChrisDenton
Copy link
Member

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.

@workingjubilee
Copy link
Member

please feel free to send a PR improving the documentation!

@Noratrieb Noratrieb added A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. A-process Area: `std::process` and `std::env` and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. C-bug Category: This is a bug. labels Jul 12, 2024
@Noratrieb
Copy link
Member

this relates to #126600, where it was clearly decided to have this function be C exit() and nothing more or less.

@RalfJung
Copy link
Member

Seems like behavior matches documentation -- thread-local variables are not on the stack. What more could the docs say?

@correabuscar

This comment was marked as outdated.

@RalfJung
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools A-process Area: `std::process` and `std::env` C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants