Skip to content

Commit 8f94c51

Browse files
committed
std: lazily allocate the main thread handle
#123550 eliminated the allocation of the main thread handle, but at the cost of greatly increased complexity. This PR proposes another approach: Instead of creating the main thread handle itself, the runtime simply remembers the thread ID of the main thread. The main thread handle is then only allocated when it is used, using the same lazy-initialization mechanism as for non-runtime use of thread::current, and the name method uses the thread ID to identify the main thread handle and return the correct name ("main") for it. Thereby, we also allow accessing thread::current before main: as the runtime no longer tries to install its own handle, this will no longer trigger an abort. Rather, the name returned from name will only be "main" after the runtime initialization code has run, but I think that is acceptable. This new approach also requires some changes to the signal handling code, as calling `thread::current` would now allocate when called on the main thread, which is not acceptable. I fixed this by adding a new function (`with_current_name`) that performs all the naming logic without allocation or without initializing the thread ID (which could allocate on some platforms).
1 parent 2a9b85b commit 8f94c51

File tree

6 files changed

+134
-99
lines changed

6 files changed

+134
-99
lines changed

library/std/src/panicking.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,16 @@ fn default_hook(info: &PanicHookInfo<'_>) {
247247
let location = info.location().unwrap();
248248

249249
let msg = payload_as_str(info.payload());
250-
let thread = thread::try_current();
251-
let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>");
252250

253251
let write = #[optimize(size)]
254252
|err: &mut dyn crate::io::Write| {
255253
// Use a lock to prevent mixed output in multithreading context.
256254
// Some platforms also require it when printing a backtrace, like `SymFromAddr` on Windows.
257255
let mut lock = backtrace::lock();
258-
let _ = writeln!(err, "thread '{name}' panicked at {location}:\n{msg}");
256+
thread::with_current_name(|name| {
257+
let name = name.unwrap_or("<unknown>");
258+
let _ = writeln!(err, "thread '{name}' panicked at {location}:\n{msg}");
259+
});
259260

260261
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
261262

library/std/src/rt.rs

+4-19
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use core::panicking::{panic_display, panic_fmt};
2323
#[rustfmt::skip]
2424
use crate::any::Any;
2525
use crate::sync::Once;
26-
use crate::thread::{self, Thread};
26+
use crate::thread::{self, main_thread};
2727
use crate::{mem, panic, sys};
2828

2929
// Prints to the "panic output", depending on the platform this may be:
@@ -102,24 +102,9 @@ unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) {
102102
sys::init(argc, argv, sigpipe)
103103
};
104104

105-
// Set up the current thread handle to give it the right name.
106-
//
107-
// When code running before main uses `ReentrantLock` (for example by
108-
// using `println!`), the thread ID can become initialized before we
109-
// create this handle. Since `set_current` fails when the ID of the
110-
// handle does not match the current ID, we should attempt to use the
111-
// current thread ID here instead of unconditionally creating a new
112-
// one. Also see #130210.
113-
let thread = Thread::new_main(thread::current_id());
114-
if let Err(_thread) = thread::set_current(thread) {
115-
// `thread::current` will create a new handle if none has been set yet.
116-
// Thus, if someone uses it before main, this call will fail. That's a
117-
// bad idea though, as we then cannot set the main thread name here.
118-
//
119-
// FIXME: detect the main thread in `thread::current` and use the
120-
// correct name there.
121-
rtabort!("code running before main must not use thread::current");
122-
}
105+
// Remember the main thread ID to give it the correct name.
106+
// SAFETY: this is the only time and place where we call this function.
107+
unsafe { main_thread::set(thread::current_id()) };
123108
}
124109

125110
/// Clean up the thread-local runtime state. This *should* be run after all other

library/std/src/sys/pal/unix/stack_overflow.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,11 @@ mod imp {
100100
// If the faulting address is within the guard page, then we print a
101101
// message saying so and abort.
102102
if start <= addr && addr < end {
103-
rtprintpanic!(
104-
"\nthread '{}' has overflowed its stack\n",
105-
thread::current().name().unwrap_or("<unknown>")
106-
);
103+
thread::with_current_name(|name| {
104+
let name = name.unwrap_or("<unknown>");
105+
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
106+
});
107+
107108
rtabort!("stack overflow");
108109
} else {
109110
// Unregister ourselves by reverting back to the default behavior.

library/std/src/sys/pal/windows/stack_overflow.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POIN
1818
let code = rec.ExceptionCode;
1919

2020
if code == c::EXCEPTION_STACK_OVERFLOW {
21-
rtprintpanic!(
22-
"\nthread '{}' has overflowed its stack\n",
23-
thread::current().name().unwrap_or("<unknown>")
24-
);
21+
thread::with_current_name(|name| {
22+
let name = name.unwrap_or("<unknown>");
23+
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
24+
});
2525
}
2626
c::EXCEPTION_CONTINUE_SEARCH
2727
}

library/std/src/thread/current.rs

+18-18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ local_pointer! {
1515
///
1616
/// We store the thread ID so that it never gets destroyed during the lifetime
1717
/// of a thread, either using `#[thread_local]` or multiple `local_pointer!`s.
18-
mod id {
18+
pub(super) mod id {
1919
use super::*;
2020

2121
cfg_if::cfg_if! {
@@ -27,7 +27,7 @@ mod id {
2727

2828
pub(super) const CHEAP: bool = true;
2929

30-
pub(super) fn get() -> Option<ThreadId> {
30+
pub(crate) fn get() -> Option<ThreadId> {
3131
ID.get()
3232
}
3333

@@ -44,7 +44,7 @@ mod id {
4444

4545
pub(super) const CHEAP: bool = false;
4646

47-
pub(super) fn get() -> Option<ThreadId> {
47+
pub(crate) fn get() -> Option<ThreadId> {
4848
let id0 = ID0.get().addr() as u64;
4949
let id16 = ID16.get().addr() as u64;
5050
let id32 = ID32.get().addr() as u64;
@@ -67,7 +67,7 @@ mod id {
6767

6868
pub(super) const CHEAP: bool = false;
6969

70-
pub(super) fn get() -> Option<ThreadId> {
70+
pub(crate) fn get() -> Option<ThreadId> {
7171
let id0 = ID0.get().addr() as u64;
7272
let id32 = ID32.get().addr() as u64;
7373
ThreadId::from_u64((id32 << 32) + id0)
@@ -85,7 +85,7 @@ mod id {
8585

8686
pub(super) const CHEAP: bool = true;
8787

88-
pub(super) fn get() -> Option<ThreadId> {
88+
pub(crate) fn get() -> Option<ThreadId> {
8989
let id = ID.get().addr() as u64;
9090
ThreadId::from_u64(id)
9191
}
@@ -112,7 +112,7 @@ mod id {
112112

113113
/// Tries to set the thread handle for the current thread. Fails if a handle was
114114
/// already set or if the thread ID of `thread` would change an already-set ID.
115-
pub(crate) fn set_current(thread: Thread) -> Result<(), Thread> {
115+
pub(super) fn set_current(thread: Thread) -> Result<(), Thread> {
116116
if CURRENT.get() != NONE {
117117
return Err(thread);
118118
}
@@ -140,28 +140,28 @@ pub(crate) fn current_id() -> ThreadId {
140140
// to retrieve it from the current thread handle, which will only take one
141141
// TLS access.
142142
if !id::CHEAP {
143-
let current = CURRENT.get();
144-
if current > DESTROYED {
145-
unsafe {
146-
let current = ManuallyDrop::new(Thread::from_raw(current));
147-
return current.id();
148-
}
143+
if let Some(id) = try_with_current(|t| t.map(|t| t.id())) {
144+
return id;
149145
}
150146
}
151147

152148
id::get_or_init()
153149
}
154150

155-
/// Gets a handle to the thread that invokes it, if the handle has been initialized.
156-
pub(crate) fn try_current() -> Option<Thread> {
151+
/// Gets a reference to the handle of the thread that invokes it, if the handle
152+
/// has been initialized.
153+
pub(super) fn try_with_current<F, R>(f: F) -> R
154+
where
155+
F: FnOnce(Option<&Thread>) -> R,
156+
{
157157
let current = CURRENT.get();
158158
if current > DESTROYED {
159159
unsafe {
160160
let current = ManuallyDrop::new(Thread::from_raw(current));
161-
Some((*current).clone())
161+
f(Some(&current))
162162
}
163163
} else {
164-
None
164+
f(None)
165165
}
166166
}
167167

@@ -176,7 +176,7 @@ pub(crate) fn current_or_unnamed() -> Thread {
176176
(*current).clone()
177177
}
178178
} else if current == DESTROYED {
179-
Thread::new_unnamed(id::get_or_init())
179+
Thread::new(id::get_or_init(), None)
180180
} else {
181181
init_current(current)
182182
}
@@ -221,7 +221,7 @@ fn init_current(current: *mut ()) -> Thread {
221221
CURRENT.set(BUSY);
222222
// If the thread ID was initialized already, use it.
223223
let id = id::get_or_init();
224-
let thread = Thread::new_unnamed(id);
224+
let thread = Thread::new(id, None);
225225

226226
// Make sure that `crate::rt::thread_cleanup` will be run, which will
227227
// call `drop_current`.

0 commit comments

Comments
 (0)