rust-lang / rust-lang/rust

`LocalKey` dropped after calling `std::process::exit()`

Open
#127,637 6 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-docs A-process C-enhancement T-libs
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the provided nightly example, then inspect std/src/process.rs and the LocalKey platform-specific documentation. Trace the reported path through std/src/sys/thread_local/native/lazy.rs and std/src/sys/pal/unix/os.rs to compare thread-local cleanup with process::exit. Done means the behavior is resolved or its contract is clearly documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.