rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.26k stars 12.57k forks source link

Main thread thread-local destructors are not always run #126858

Closed RalfJung closed 2 months ago

RalfJung commented 3 months ago

Simplified testcase:

struct Foo;

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Foo dtor");
    }
}

thread_local!(static FOO: Foo = Foo);

fn main() {
    FOO.with(|_| {});
}

This should print Foo dtor, but prints nothing on some targets:

Here's a more complicated testcase that also involves initializing a destructor while destructors are being run; ideally this will be added to the test suite at some point:

struct Bar;

impl Drop for Bar {
    fn drop(&mut self) {
        println!("Bar dtor");
    }
}

struct Foo;

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Foo dtor");
        // We initialize another thread-local inside the dtor, which is an interesting corner case.
        thread_local!(static BAR: Bar = Bar);
        BAR.with(|_| {});
    }
}

thread_local!(static FOO: Foo = Foo);

fn main() {
    FOO.with(|_| {});
}
bjorn3 commented 3 months ago

For musl the thread-local destructors registered with pthread_key_create seem to get run by __pthread_tsd_run_dtors, which is only called from pthread_exit. For bionic (Android's libc) they get run from pthread_key_clean_all, which too is only called from pthread_exit. It doesn't seem to be documented anywhere officially if exiting the main thread without pthread_exit should invoke thread-local destructors. Several stack overflow posts say it doesn't which is the behavior observed for musl and bionic, while glibc has been calling them since 2004 (https://github.com/bminor/glibc/commit/3fa21fd813ac323f2890812b99663d7cf17578eb).

RalfJung commented 2 months ago

Closing in favor of the original, https://github.com/rust-lang/rust/issues/28129.