Skip to content

Commit fd1f8aa

Browse files
committed
Auto merge of #127912 - joboet:tls_dtor_thread_current, r=cuviper
std: make `thread::current` available in all `thread_local!` destructors ... and thereby allow the panic runtime to always print the right thread name. This works by modifying the TLS destructor system to schedule a runtime cleanup function after all other TLS destructors registered by `std` have run. Unfortunately, this doesn't affect foreign TLS destructors, `thread::current` will still panic there. Additionally, the thread ID returned by `current_id` will now always be available, even inside the global allocator, and will not change during the lifetime of one thread (this was previously the case with key-based TLS). The mechanisms I added for this (`local_pointer` and `thread_cleanup`) will also allow finally fixing #111272 by moving the signal stack to a similar runtime-cleanup TLS variable.
2 parents ad9c494 + a71ba0c commit fd1f8aa

File tree

21 files changed

+536
-139
lines changed

21 files changed

+536
-139
lines changed

library/std/src/rt.rs

+25-9
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ pub use crate::panicking::{begin_panic, panic_count};
2121
pub use core::panicking::{panic_display, panic_fmt};
2222

2323
#[rustfmt::skip]
24+
use crate::any::Any;
2425
use crate::sync::Once;
25-
use crate::sys;
2626
use crate::thread::{self, Thread};
27+
use crate::{mem, panic, sys};
2728

2829
// Prints to the "panic output", depending on the platform this may be:
2930
// - the standard error output
@@ -66,6 +67,11 @@ macro_rules! rtunwrap {
6667
};
6768
}
6869

70+
fn handle_rt_panic(e: Box<dyn Any + Send>) {
71+
mem::forget(e);
72+
rtabort!("initialization or cleanup bug");
73+
}
74+
6975
// One-time runtime initialization.
7076
// Runs before `main`.
7177
// SAFETY: must be called only once during runtime initialization.
@@ -101,6 +107,20 @@ unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
101107
thread::set_current(thread);
102108
}
103109

110+
/// Clean up the thread-local runtime state. This *should* be run after all other
111+
/// code managed by the Rust runtime, but will not cause UB if that condition is
112+
/// not fulfilled. Also note that this function is not guaranteed to be run, but
113+
/// skipping it will cause leaks and therefore is to be avoided.
114+
pub(crate) fn thread_cleanup() {
115+
// This function is run in situations where unwinding leads to an abort
116+
// (think `extern "C"` functions). Abort here instead so that we can
117+
// print a nice message.
118+
panic::catch_unwind(|| {
119+
crate::thread::drop_current();
120+
})
121+
.unwrap_or_else(handle_rt_panic);
122+
}
123+
104124
// One-time runtime cleanup.
105125
// Runs after `main` or at program exit.
106126
// NOTE: this is not guaranteed to run, for example when the program aborts.
@@ -123,11 +143,6 @@ fn lang_start_internal(
123143
argv: *const *const u8,
124144
sigpipe: u8,
125145
) -> Result<isize, !> {
126-
use crate::{mem, panic};
127-
let rt_abort = move |e| {
128-
mem::forget(e);
129-
rtabort!("initialization or cleanup bug");
130-
};
131146
// Guard against the code called by this function from unwinding outside of the Rust-controlled
132147
// code, which is UB. This is a requirement imposed by a combination of how the
133148
// `#[lang="start"]` attribute is implemented as well as by the implementation of the panicking
@@ -139,16 +154,17 @@ fn lang_start_internal(
139154
// prevent std from accidentally introducing a panic to these functions. Another is from
140155
// user code from `main` or, more nefariously, as described in e.g. issue #86030.
141156
// SAFETY: Only called once during runtime initialization.
142-
panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_err(rt_abort)?;
157+
panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) })
158+
.unwrap_or_else(handle_rt_panic);
143159
let ret_code = panic::catch_unwind(move || panic::catch_unwind(main).unwrap_or(101) as isize)
144160
.map_err(move |e| {
145161
mem::forget(e);
146162
rtabort!("drop of the panic payload panicked");
147163
});
148-
panic::catch_unwind(cleanup).map_err(rt_abort)?;
164+
panic::catch_unwind(cleanup).unwrap_or_else(handle_rt_panic);
149165
// Guard against multiple threads calling `libc::exit` concurrently.
150166
// See the documentation for `unique_thread_exit` for more information.
151-
panic::catch_unwind(|| crate::sys::exit_guard::unique_thread_exit()).map_err(rt_abort)?;
167+
panic::catch_unwind(crate::sys::exit_guard::unique_thread_exit).unwrap_or_else(handle_rt_panic);
152168
ret_code
153169
}
154170

library/std/src/sys/pal/hermit/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ pub unsafe extern "C" fn runtime_entry(
9292
unsafe {
9393
crate::sys::thread_local::destructors::run();
9494
}
95-
unsafe { hermit_abi::exit(result) }
95+
crate::rt::thread_cleanup();
96+
97+
unsafe {
98+
hermit_abi::exit(result);
99+
}
96100
}
97101

98102
#[inline]

library/std/src/sys/pal/hermit/thread.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ impl Thread {
5353

5454
// run all destructors
5555
crate::sys::thread_local::destructors::run();
56+
crate::rt::thread_cleanup();
5657
}
5758
}
5859
}

library/std/src/sys/sync/rwlock/queue.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ use crate::mem;
113113
use crate::ptr::{self, NonNull, null_mut, without_provenance_mut};
114114
use crate::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
115115
use crate::sync::atomic::{AtomicBool, AtomicPtr};
116-
use crate::thread::{self, Thread};
116+
use crate::thread::{self, Thread, ThreadId};
117117

118118
// Locking uses exponential backoff. `SPIN_COUNT` indicates how many times the
119119
// locking operation will be retried.
@@ -200,7 +200,9 @@ impl Node {
200200
fn prepare(&mut self) {
201201
// Fall back to creating an unnamed `Thread` handle to allow locking in
202202
// TLS destructors.
203-
self.thread.get_or_init(|| thread::try_current().unwrap_or_else(Thread::new_unnamed));
203+
self.thread.get_or_init(|| {
204+
thread::try_current().unwrap_or_else(|| Thread::new_unnamed(ThreadId::new()))
205+
});
204206
self.completed = AtomicBool::new(false);
205207
}
206208

library/std/src/sys/thread_local/guard/apple.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub fn enable() {
2626
unsafe extern "C" fn run_dtors(_: *mut u8) {
2727
unsafe {
2828
destructors::run();
29+
crate::rt::thread_cleanup();
2930
}
3031
}
3132
}

library/std/src/sys/thread_local/guard/key.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
//! that will run all native TLS destructors in the destructor list.
44
55
use crate::ptr;
6-
use crate::sys::thread_local::destructors;
76
use crate::sys::thread_local::key::{LazyKey, set};
87

8+
#[cfg(target_thread_local)]
99
pub fn enable() {
10+
use crate::sys::thread_local::destructors;
11+
1012
static DTORS: LazyKey = LazyKey::new(Some(run));
1113

1214
// Setting the key value to something other than NULL will result in the
@@ -18,6 +20,41 @@ pub fn enable() {
1820
unsafe extern "C" fn run(_: *mut u8) {
1921
unsafe {
2022
destructors::run();
23+
// On platforms with `__cxa_thread_atexit_impl`, `destructors::run`
24+
// does nothing on newer systems as the TLS destructors are
25+
// registered with the system. But because all of those platforms
26+
// call the destructors of TLS keys after the registered ones, this
27+
// function will still be run last (at the time of writing).
28+
crate::rt::thread_cleanup();
29+
}
30+
}
31+
}
32+
33+
/// On platforms with key-based TLS, the system runs the destructors for us.
34+
/// We still have to make sure that [`crate::rt::thread_cleanup`] is called,
35+
/// however. This is done by defering the execution of a TLS destructor to
36+
/// the next round of destruction inside the TLS destructors.
37+
#[cfg(not(target_thread_local))]
38+
pub fn enable() {
39+
const DEFER: *mut u8 = ptr::without_provenance_mut(1);
40+
const RUN: *mut u8 = ptr::without_provenance_mut(2);
41+
42+
static CLEANUP: LazyKey = LazyKey::new(Some(run));
43+
44+
unsafe { set(CLEANUP.force(), DEFER) }
45+
46+
unsafe extern "C" fn run(state: *mut u8) {
47+
if state == DEFER {
48+
// Make sure that this function is run again in the next round of
49+
// TLS destruction. If there is no futher round, there will be leaks,
50+
// but that's okay, `thread_cleanup` is not guaranteed to be called.
51+
unsafe { set(CLEANUP.force(), RUN) }
52+
} else {
53+
debug_assert_eq!(state, RUN);
54+
// If the state is still RUN in the next round of TLS destruction,
55+
// it means that no other TLS destructors defined by this runtime
56+
// have been run, as they would have set the state to DEFER.
57+
crate::rt::thread_cleanup();
2158
}
2259
}
2360
}

library/std/src/sys/thread_local/guard/solid.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ pub fn enable() {
1919
}
2020

2121
unsafe extern "C" fn tls_dtor(_unused: *mut u8) {
22-
unsafe { destructors::run() };
22+
unsafe {
23+
destructors::run();
24+
crate::rt::thread_cleanup();
25+
}
2326
}
2427
}

library/std/src/sys/thread_local/guard/windows.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,13 @@ pub static CALLBACK: unsafe extern "system" fn(*mut c_void, u32, *mut c_void) =
8080

8181
unsafe extern "system" fn tls_callback(_h: *mut c_void, dw_reason: u32, _pv: *mut c_void) {
8282
if dw_reason == c::DLL_THREAD_DETACH || dw_reason == c::DLL_PROCESS_DETACH {
83-
#[cfg(target_thread_local)]
8483
unsafe {
84+
#[cfg(target_thread_local)]
8585
super::super::destructors::run();
86-
}
87-
#[cfg(not(target_thread_local))]
88-
unsafe {
86+
#[cfg(not(target_thread_local))]
8987
super::super::key::run_dtors();
88+
89+
crate::rt::thread_cleanup();
9090
}
9191
}
9292
}

library/std/src/sys/thread_local/key/xous.rs

+2
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,6 @@ unsafe fn run_dtors() {
212212
unsafe { cur = (*cur).next };
213213
}
214214
}
215+
216+
crate::rt::thread_cleanup();
215217
}

library/std/src/sys/thread_local/mod.rs

+31-17
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ cfg_if::cfg_if! {
3131
))] {
3232
mod statik;
3333
pub use statik::{EagerStorage, LazyStorage, thread_local_inner};
34+
pub(crate) use statik::{LocalPointer, local_pointer};
3435
} else if #[cfg(target_thread_local)] {
3536
mod native;
3637
pub use native::{EagerStorage, LazyStorage, thread_local_inner};
38+
pub(crate) use native::{LocalPointer, local_pointer};
3739
} else {
3840
mod os;
3941
pub use os::{Storage, thread_local_inner};
42+
pub(crate) use os::{LocalPointer, local_pointer};
4043
}
4144
}
4245

@@ -72,36 +75,47 @@ pub(crate) mod destructors {
7275
}
7376

7477
/// This module provides a way to schedule the execution of the destructor list
75-
/// on systems without a per-variable destructor system.
76-
mod guard {
78+
/// and the [runtime cleanup](crate::rt::thread_cleanup) function. Calling `enable`
79+
/// should ensure that these functions are called at the right times.
80+
pub(crate) mod guard {
7781
cfg_if::cfg_if! {
7882
if #[cfg(all(target_thread_local, target_vendor = "apple"))] {
7983
mod apple;
80-
pub(super) use apple::enable;
84+
pub(crate) use apple::enable;
8185
} else if #[cfg(target_os = "windows")] {
8286
mod windows;
83-
pub(super) use windows::enable;
87+
pub(crate) use windows::enable;
8488
} else if #[cfg(any(
85-
all(target_family = "wasm", target_feature = "atomics"),
89+
target_family = "wasm",
90+
target_os = "uefi",
91+
target_os = "zkvm",
8692
))] {
87-
pub(super) fn enable() {
88-
// FIXME: Right now there is no concept of "thread exit", but
89-
// this is likely going to show up at some point in the form of
90-
// an exported symbol that the wasm runtime is going to be
91-
// expected to call. For now we just leak everything, but if
92-
// such a function starts to exist it will probably need to
93-
// iterate the destructor list with this function:
93+
pub(crate) fn enable() {
94+
// FIXME: Right now there is no concept of "thread exit" on
95+
// wasm, but this is likely going to show up at some point in
96+
// the form of an exported symbol that the wasm runtime is going
97+
// to be expected to call. For now we just leak everything, but
98+
// if such a function starts to exist it will probably need to
99+
// iterate the destructor list with these functions:
100+
#[cfg(all(target_family = "wasm", target_feature = "atomics"))]
94101
#[allow(unused)]
95102
use super::destructors::run;
103+
#[allow(unused)]
104+
use crate::rt::thread_cleanup;
96105
}
97-
} else if #[cfg(target_os = "hermit")] {
98-
pub(super) fn enable() {}
106+
} else if #[cfg(any(
107+
target_os = "hermit",
108+
target_os = "xous",
109+
))] {
110+
// `std` is the only runtime, so it just calls the destructor functions
111+
// itself when the time comes.
112+
pub(crate) fn enable() {}
99113
} else if #[cfg(target_os = "solid_asp3")] {
100114
mod solid;
101-
pub(super) use solid::enable;
102-
} else if #[cfg(all(target_thread_local, not(target_family = "wasm")))] {
115+
pub(crate) use solid::enable;
116+
} else {
103117
mod key;
104-
pub(super) use key::enable;
118+
pub(crate) use key::enable;
105119
}
106120
}
107121
}

library/std/src/sys/thread_local/native/mod.rs

+31
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
//! eliminates the `Destroyed` state for these values, which can allow more niche
3030
//! optimizations to occur for the `State` enum. For `Drop` types, `()` is used.
3131
32+
use crate::cell::Cell;
33+
use crate::ptr;
34+
3235
mod eager;
3336
mod lazy;
3437

@@ -107,3 +110,31 @@ pub macro thread_local_inner {
107110
$crate::thread::local_impl::thread_local_inner!(@key $t, $($init)*);
108111
},
109112
}
113+
114+
#[rustc_macro_transparency = "semitransparent"]
115+
pub(crate) macro local_pointer {
116+
() => {},
117+
($vis:vis static $name:ident; $($rest:tt)*) => {
118+
#[thread_local]
119+
$vis static $name: $crate::sys::thread_local::LocalPointer = $crate::sys::thread_local::LocalPointer::__new();
120+
$crate::sys::thread_local::local_pointer! { $($rest)* }
121+
},
122+
}
123+
124+
pub(crate) struct LocalPointer {
125+
p: Cell<*mut ()>,
126+
}
127+
128+
impl LocalPointer {
129+
pub const fn __new() -> LocalPointer {
130+
LocalPointer { p: Cell::new(ptr::null_mut()) }
131+
}
132+
133+
pub fn get(&self) -> *mut () {
134+
self.p.get()
135+
}
136+
137+
pub fn set(&self, p: *mut ()) {
138+
self.p.set(p)
139+
}
140+
}

library/std/src/sys/thread_local/os.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use super::abort_on_dtor_unwind;
1+
use super::key::{Key, LazyKey, get, set};
2+
use super::{abort_on_dtor_unwind, guard};
23
use crate::cell::Cell;
34
use crate::marker::PhantomData;
45
use crate::ptr;
5-
use crate::sys::thread_local::key::{Key, LazyKey, get, set};
66

77
#[doc(hidden)]
88
#[allow_internal_unstable(thread_local_internals)]
@@ -138,5 +138,35 @@ unsafe extern "C" fn destroy_value<T: 'static>(ptr: *mut u8) {
138138
drop(ptr);
139139
// SAFETY: `key` is the TLS key `ptr` was stored under.
140140
unsafe { set(key, ptr::null_mut()) };
141+
// Make sure that the runtime cleanup will be performed
142+
// after the next round of TLS destruction.
143+
guard::enable();
141144
});
142145
}
146+
147+
#[rustc_macro_transparency = "semitransparent"]
148+
pub(crate) macro local_pointer {
149+
() => {},
150+
($vis:vis static $name:ident; $($rest:tt)*) => {
151+
$vis static $name: $crate::sys::thread_local::LocalPointer = $crate::sys::thread_local::LocalPointer::__new();
152+
$crate::sys::thread_local::local_pointer! { $($rest)* }
153+
},
154+
}
155+
156+
pub(crate) struct LocalPointer {
157+
key: LazyKey,
158+
}
159+
160+
impl LocalPointer {
161+
pub const fn __new() -> LocalPointer {
162+
LocalPointer { key: LazyKey::new(None) }
163+
}
164+
165+
pub fn get(&'static self) -> *mut () {
166+
unsafe { get(self.key.force()) as *mut () }
167+
}
168+
169+
pub fn set(&'static self, p: *mut ()) {
170+
unsafe { set(self.key.force(), p as *mut u8) }
171+
}
172+
}

0 commit comments

Comments
 (0)