Skip to main content

std/sys/thread_local/guard/
key.rs

1//! A lot of UNIX platforms don't have a specialized way to register TLS destructors for native TLS
2//! (i.e., `#[thread_local] static`). Instead, we use one TLS key with a destructor that will run
3//! all native TLS destructors in the destructor list. It also runs `rt::thread_cleanup`.
4
5#[cfg(target_thread_local)]
6pub fn enable() {
7    use crate::ptr;
8    use crate::sys::thread_local::destructors;
9    use crate::sys::thread_local::key::{LazyKey, set};
10
11    static DTORS: LazyKey = LazyKey::new(Some(run));
12
13    // Setting the key value to something other than NULL will result in the
14    // destructor being run at thread exit.
15    unsafe {
16        set(DTORS.force(), ptr::without_provenance_mut(1));
17    }
18
19    unsafe extern "C" fn run(_: *mut u8) {
20        unsafe {
21            // Rust thread-local destructors should run *before* `rt::thread_cleanup`.
22            destructors::run();
23            // On platforms with `__cxa_thread_atexit_impl`, `destructors::run` does nothing as the
24            // TLS destructors are registered with the system. But because all of those platforms
25            // call the destructors of TLS keys *after* the registered "atexit" ones, this function
26            // will still be run last (at the time of writing). The `thread_current_in_dtor` test
27            // confirms this.
28            crate::rt::thread_cleanup();
29        }
30    }
31}
32
33#[cfg(not(target_thread_local))]
34pub use key_based::{LOCAL_POINTER_NEEDS_DTOR, cleanup_pending, enable};
35
36#[cfg(not(target_thread_local))]
37mod key_based {
38    use crate::ptr;
39    use crate::sys::thread_local::key::{LazyKey, get, set};
40
41    const DEFER: *mut u8 = ptr::without_provenance_mut(1);
42    const RUN: *mut u8 = ptr::without_provenance_mut(2);
43
44    static CLEANUP: LazyKey = LazyKey::new(Some(run));
45
46    /// On platforms with key-based TLS, the system runs the destructors for us, so we don't have to
47    /// call `destructors::run`. We still have to make sure that [`crate::rt::thread_cleanup`] is
48    /// called, however. And we should make sure that it is called *last*, after dropping all
49    /// regular Rust thread-locals. This is done by deferring the execution of a TLS destructor to
50    /// the next round of destruction inside the TLS destructors.
51    pub fn enable() {
52        unsafe { set(CLEANUP.force(), DEFER) }
53    }
54
55    unsafe extern "C" fn run(state: *mut u8) {
56        if state == DEFER {
57            // Make sure that this function is run again in the next round of
58            // TLS destruction. If there is no further round, there will be leaks,
59            // but that's okay, `thread_cleanup` is not guaranteed to be called.
60            unsafe { set(CLEANUP.force(), RUN) }
61        } else {
62            debug_assert_eq!(state, RUN);
63            // If the state is still RUN in the next round of TLS destruction,
64            // it means that no other TLS destructors defined by this runtime
65            // have been run, as they would have set the state to DEFER.
66            crate::rt::thread_cleanup();
67        }
68    }
69
70    /// Whether [`crate::rt::thread_cleanup`] has yet to run on this thread.
71    pub fn cleanup_pending() -> bool {
72        !unsafe { get(CLEANUP.force()) }.is_null()
73    }
74
75    /// The OS may free the values of the keys without a destructor **before**
76    /// [`crate::rt::thread_cleanup`] runs (e.g. ESP-IDF, glibc and musl free
77    /// them in the first round of TLS destruction). Hence, local pointers need
78    /// a destructor, which stores the value again for as long as
79    /// [`cleanup_pending`] returns `true`.
80    pub const LOCAL_POINTER_NEEDS_DTOR: bool = true;
81}