Skip to content

Commit 40735d3

Browse files
authored
Rollup merge of rust-lang#125405 - m-ou-se:thread-add-spawn-hook, r=WaffleLapkin
Add std::thread::add_spawn_hook. Implementation of rust-lang/rfcs#3642
2 parents 6c20348 + 2089cb3 commit 40735d3

File tree

3 files changed

+186
-7
lines changed

3 files changed

+186
-7
lines changed

std/src/thread/mod.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ mod current;
188188
pub use current::current;
189189
pub(crate) use current::{current_id, drop_current, set_current, try_current};
190190

191+
mod spawnhook;
192+
193+
#[unstable(feature = "thread_spawn_hook", issue = "132951")]
194+
pub use spawnhook::add_spawn_hook;
195+
191196
////////////////////////////////////////////////////////////////////////////////
192197
// Thread-local storage
193198
////////////////////////////////////////////////////////////////////////////////
@@ -259,6 +264,8 @@ pub struct Builder {
259264
name: Option<String>,
260265
// The size of the stack for the spawned thread in bytes
261266
stack_size: Option<usize>,
267+
// Skip running and inheriting the thread spawn hooks
268+
no_hooks: bool,
262269
}
263270

264271
impl Builder {
@@ -282,7 +289,7 @@ impl Builder {
282289
/// ```
283290
#[stable(feature = "rust1", since = "1.0.0")]
284291
pub fn new() -> Builder {
285-
Builder { name: None, stack_size: None }
292+
Builder { name: None, stack_size: None, no_hooks: false }
286293
}
287294

288295
/// Names the thread-to-be. Currently the name is used for identification
@@ -338,6 +345,16 @@ impl Builder {
338345
self
339346
}
340347

348+
/// Disables running and inheriting [spawn hooks](add_spawn_hook).
349+
///
350+
/// Use this if the parent thread is in no way relevant for the child thread.
351+
/// For example, when lazily spawning threads for a thread pool.
352+
#[unstable(feature = "thread_spawn_hook", issue = "132951")]
353+
pub fn no_hooks(mut self) -> Builder {
354+
self.no_hooks = true;
355+
self
356+
}
357+
341358
/// Spawns a new thread by taking ownership of the `Builder`, and returns an
342359
/// [`io::Result`] to its [`JoinHandle`].
343360
///
@@ -460,7 +477,7 @@ impl Builder {
460477
F: Send,
461478
T: Send,
462479
{
463-
let Builder { name, stack_size } = self;
480+
let Builder { name, stack_size, no_hooks } = self;
464481

465482
let stack_size = stack_size.unwrap_or_else(|| {
466483
static MIN: AtomicUsize = AtomicUsize::new(0);
@@ -485,6 +502,13 @@ impl Builder {
485502
Some(name) => Thread::new(id, name.into()),
486503
None => Thread::new_unnamed(id),
487504
};
505+
506+
let hooks = if no_hooks {
507+
spawnhook::ChildSpawnHooks::default()
508+
} else {
509+
spawnhook::run_spawn_hooks(&my_thread)
510+
};
511+
488512
let their_thread = my_thread.clone();
489513

490514
let my_packet: Arc<Packet<'scope, T>> = Arc::new(Packet {
@@ -494,9 +518,6 @@ impl Builder {
494518
});
495519
let their_packet = my_packet.clone();
496520

497-
let output_capture = crate::io::set_output_capture(None);
498-
crate::io::set_output_capture(output_capture.clone());
499-
500521
// Pass `f` in `MaybeUninit` because actually that closure might *run longer than the lifetime of `F`*.
501522
// See <https://github.com/rust-lang/rust/issues/101983> for more details.
502523
// To prevent leaks we use a wrapper that drops its contents.
@@ -534,10 +555,9 @@ impl Builder {
534555
imp::Thread::set_name(name);
535556
}
536557

537-
crate::io::set_output_capture(output_capture);
538-
539558
let f = f.into_inner();
540559
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
560+
crate::sys::backtrace::__rust_begin_short_backtrace(|| hooks.run());
541561
crate::sys::backtrace::__rust_begin_short_backtrace(f)
542562
}));
543563
// SAFETY: `their_packet` as been built just above and moved by the

std/src/thread/spawnhook.rs

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use crate::cell::Cell;
2+
use crate::iter;
3+
use crate::sync::Arc;
4+
use crate::thread::Thread;
5+
6+
crate::thread_local! {
7+
/// A thread local linked list of spawn hooks.
8+
///
9+
/// It is a linked list of Arcs, such that it can very cheaply be inhereted by spawned threads.
10+
///
11+
/// (That technically makes it a set of linked lists with shared tails, so a linked tree.)
12+
static SPAWN_HOOKS: Cell<SpawnHooks> = const { Cell::new(SpawnHooks { first: None }) };
13+
}
14+
15+
#[derive(Default, Clone)]
16+
struct SpawnHooks {
17+
first: Option<Arc<SpawnHook>>,
18+
}
19+
20+
// Manually implement drop to prevent deep recursion when dropping linked Arc list.
21+
impl Drop for SpawnHooks {
22+
fn drop(&mut self) {
23+
let mut next = self.first.take();
24+
while let Some(SpawnHook { hook, next: n }) = next.and_then(|n| Arc::into_inner(n)) {
25+
drop(hook);
26+
next = n;
27+
}
28+
}
29+
}
30+
31+
struct SpawnHook {
32+
hook: Box<dyn Send + Sync + Fn(&Thread) -> Box<dyn Send + FnOnce()>>,
33+
next: Option<Arc<SpawnHook>>,
34+
}
35+
36+
/// Registers a function to run for every newly thread spawned.
37+
///
38+
/// The hook is executed in the parent thread, and returns a function
39+
/// that will be executed in the new thread.
40+
///
41+
/// The hook is called with the `Thread` handle for the new thread.
42+
///
43+
/// The hook will only be added for the current thread and is inherited by the threads it spawns.
44+
/// In other words, adding a hook has no effect on already running threads (other than the current
45+
/// thread) and the threads they might spawn in the future.
46+
///
47+
/// Hooks can only be added, not removed.
48+
///
49+
/// The hooks will run in reverse order, starting with the most recently added.
50+
///
51+
/// # Usage
52+
///
53+
/// ```
54+
/// #![feature(thread_spawn_hook)]
55+
///
56+
/// std::thread::add_spawn_hook(|_| {
57+
/// ..; // This will run in the parent (spawning) thread.
58+
/// move || {
59+
/// ..; // This will run it the child (spawned) thread.
60+
/// }
61+
/// });
62+
/// ```
63+
///
64+
/// # Example
65+
///
66+
/// A spawn hook can be used to "inherit" a thread local from the parent thread:
67+
///
68+
/// ```
69+
/// #![feature(thread_spawn_hook)]
70+
///
71+
/// use std::cell::Cell;
72+
///
73+
/// thread_local! {
74+
/// static X: Cell<u32> = Cell::new(0);
75+
/// }
76+
///
77+
/// // This needs to be done once in the main thread before spawning any threads.
78+
/// std::thread::add_spawn_hook(|_| {
79+
/// // Get the value of X in the spawning thread.
80+
/// let value = X.get();
81+
/// // Set the value of X in the newly spawned thread.
82+
/// move || X.set(value)
83+
/// });
84+
///
85+
/// X.set(123);
86+
///
87+
/// std::thread::spawn(|| {
88+
/// assert_eq!(X.get(), 123);
89+
/// }).join().unwrap();
90+
/// ```
91+
#[unstable(feature = "thread_spawn_hook", issue = "132951")]
92+
pub fn add_spawn_hook<F, G>(hook: F)
93+
where
94+
F: 'static + Send + Sync + Fn(&Thread) -> G,
95+
G: 'static + Send + FnOnce(),
96+
{
97+
SPAWN_HOOKS.with(|h| {
98+
let mut hooks = h.take();
99+
let next = hooks.first.take();
100+
hooks.first = Some(Arc::new(SpawnHook {
101+
hook: Box::new(move |thread| Box::new(hook(thread))),
102+
next,
103+
}));
104+
h.set(hooks);
105+
});
106+
}
107+
108+
/// Runs all the spawn hooks.
109+
///
110+
/// Called on the parent thread.
111+
///
112+
/// Returns the functions to be called on the newly spawned thread.
113+
pub(super) fn run_spawn_hooks(thread: &Thread) -> ChildSpawnHooks {
114+
// Get a snapshot of the spawn hooks.
115+
// (Increments the refcount to the first node.)
116+
let hooks = SPAWN_HOOKS.with(|hooks| {
117+
let snapshot = hooks.take();
118+
hooks.set(snapshot.clone());
119+
snapshot
120+
});
121+
// Iterate over the hooks, run them, and collect the results in a vector.
122+
let to_run: Vec<_> = iter::successors(hooks.first.as_deref(), |hook| hook.next.as_deref())
123+
.map(|hook| (hook.hook)(thread))
124+
.collect();
125+
// Pass on the snapshot of the hooks and the results to the new thread,
126+
// which will then run SpawnHookResults::run().
127+
ChildSpawnHooks { hooks, to_run }
128+
}
129+
130+
/// The results of running the spawn hooks.
131+
///
132+
/// This struct is sent to the new thread.
133+
/// It contains the inherited hooks and the closures to be run.
134+
#[derive(Default)]
135+
pub(super) struct ChildSpawnHooks {
136+
hooks: SpawnHooks,
137+
to_run: Vec<Box<dyn FnOnce() + Send>>,
138+
}
139+
140+
impl ChildSpawnHooks {
141+
// This is run on the newly spawned thread, directly at the start.
142+
pub(super) fn run(self) {
143+
SPAWN_HOOKS.set(self.hooks);
144+
for run in self.to_run {
145+
run();
146+
}
147+
}
148+
}

test/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#![feature(process_exitcode_internals)]
2525
#![feature(panic_can_unwind)]
2626
#![feature(test)]
27+
#![feature(thread_spawn_hook)]
2728
#![allow(internal_features)]
2829
#![warn(rustdoc::unescaped_backticks)]
2930

@@ -134,6 +135,16 @@ pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Option<Opt
134135
}
135136
});
136137
panic::set_hook(hook);
138+
// Use a thread spawning hook to make new threads inherit output capturing.
139+
std::thread::add_spawn_hook(|_| {
140+
// Get and clone the output capture of the current thread.
141+
let output_capture = io::set_output_capture(None);
142+
io::set_output_capture(output_capture.clone());
143+
// Set the output capture of the new thread.
144+
|| {
145+
io::set_output_capture(output_capture);
146+
}
147+
});
137148
}
138149
let res = console::run_tests_console(&opts, tests);
139150
// Prevent Valgrind from reporting reachable blocks in users' unit tests.

0 commit comments

Comments
 (0)