about summary refs log tree commit diff
path: root/library/std/src/thread
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src/thread')
-rw-r--r--library/std/src/thread/current.rs31
-rw-r--r--library/std/src/thread/local.rs30
-rw-r--r--library/std/src/thread/mod.rs49
-rw-r--r--library/std/src/thread/scoped.rs9
-rw-r--r--library/std/src/thread/spawnhook.rs148
5 files changed, 244 insertions, 23 deletions
diff --git a/library/std/src/thread/current.rs b/library/std/src/thread/current.rs
index e6eb90c4c30..1048ef97356 100644
--- a/library/std/src/thread/current.rs
+++ b/library/std/src/thread/current.rs
@@ -165,6 +165,23 @@ pub(crate) fn try_current() -> Option<Thread> {
     }
 }
 
+/// Gets a handle to the thread that invokes it. If the handle stored in thread-
+/// local storage was already destroyed, this creates a new unnamed temporary
+/// handle to allow thread parking in nearly all situations.
+pub(crate) fn current_or_unnamed() -> Thread {
+    let current = CURRENT.get();
+    if current > DESTROYED {
+        unsafe {
+            let current = ManuallyDrop::new(Thread::from_raw(current));
+            (*current).clone()
+        }
+    } else if current == DESTROYED {
+        Thread::new_unnamed(id::get_or_init())
+    } else {
+        init_current(current)
+    }
+}
+
 /// Gets a handle to the thread that invokes it.
 ///
 /// # Examples
@@ -226,17 +243,17 @@ fn init_current(current: *mut ()) -> Thread {
         // a particular API should be entirely allocation-free, feel free to open
         // an issue on the Rust repository, we'll see what we can do.
         rtabort!(
-            "\n
-            Attempted to access thread-local data while allocating said data.\n
-            Do not access functions that allocate in the global allocator!\n
-            This is a bug in the global allocator.\n
-        "
+            "\n\
+            Attempted to access thread-local data while allocating said data.\n\
+            Do not access functions that allocate in the global allocator!\n\
+            This is a bug in the global allocator.\n\
+            "
         )
     } else {
         debug_assert_eq!(current, DESTROYED);
         panic!(
-            "use of std::thread::current() is not possible after the thread's
-         local data has been destroyed"
+            "use of std::thread::current() is not possible after the thread's \
+            local data has been destroyed"
         )
     }
 }
diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs
index 9edb3fa4193..bc5f701e346 100644
--- a/library/std/src/thread/local.rs
+++ b/library/std/src/thread/local.rs
@@ -252,6 +252,19 @@ impl<T: 'static> LocalKey<T> {
     /// This function will `panic!()` if the key currently has its
     /// destructor running, and it **may** panic if the destructor has
     /// previously been run for this thread.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// thread_local! {
+    ///     pub static STATIC: String = String::from("I am");
+    /// }
+    ///
+    /// assert_eq!(
+    ///     STATIC.with(|original_value| format!("{original_value} initialized")),
+    ///     "I am initialized",
+    /// );
+    /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn with<F, R>(&'static self, f: F) -> R
     where
@@ -273,6 +286,19 @@ impl<T: 'static> LocalKey<T> {
     ///
     /// This function will still `panic!()` if the key is uninitialized and the
     /// key's initializer panics.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// thread_local! {
+    ///     pub static STATIC: String = String::from("I am");
+    /// }
+    ///
+    /// assert_eq!(
+    ///     STATIC.try_with(|original_value| format!("{original_value} initialized")),
+    ///     Ok(String::from("I am initialized")),
+    /// );
+    /// ```
     #[stable(feature = "thread_local_try_with", since = "1.26.0")]
     #[inline]
     pub fn try_with<F, R>(&'static self, f: F) -> Result<R, AccessError>
@@ -452,7 +478,7 @@ impl<T: 'static> LocalKey<RefCell<T>> {
     /// Panics if the key currently has its destructor running,
     /// and it **may** panic if the destructor has previously been run for this thread.
     ///
-    /// # Example
+    /// # Examples
     ///
     /// ```
     /// use std::cell::RefCell;
@@ -483,7 +509,7 @@ impl<T: 'static> LocalKey<RefCell<T>> {
     /// Panics if the key currently has its destructor running,
     /// and it **may** panic if the destructor has previously been run for this thread.
     ///
-    /// # Example
+    /// # Examples
     ///
     /// ```
     /// use std::cell::RefCell;
diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs
index 227ee9d64f3..2ff44fcd4c6 100644
--- a/library/std/src/thread/mod.rs
+++ b/library/std/src/thread/mod.rs
@@ -186,7 +186,12 @@ mod current;
 
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use current::current;
-pub(crate) use current::{current_id, drop_current, set_current, try_current};
+pub(crate) use current::{current_id, current_or_unnamed, drop_current, set_current, try_current};
+
+mod spawnhook;
+
+#[unstable(feature = "thread_spawn_hook", issue = "132951")]
+pub use spawnhook::add_spawn_hook;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Thread-local storage
@@ -259,6 +264,8 @@ pub struct Builder {
     name: Option<String>,
     // The size of the stack for the spawned thread in bytes
     stack_size: Option<usize>,
+    // Skip running and inheriting the thread spawn hooks
+    no_hooks: bool,
 }
 
 impl Builder {
@@ -282,7 +289,7 @@ impl Builder {
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn new() -> Builder {
-        Builder { name: None, stack_size: None }
+        Builder { name: None, stack_size: None, no_hooks: false }
     }
 
     /// Names the thread-to-be. Currently the name is used for identification
@@ -338,6 +345,16 @@ impl Builder {
         self
     }
 
+    /// Disables running and inheriting [spawn hooks](add_spawn_hook).
+    ///
+    /// Use this if the parent thread is in no way relevant for the child thread.
+    /// For example, when lazily spawning threads for a thread pool.
+    #[unstable(feature = "thread_spawn_hook", issue = "132951")]
+    pub fn no_hooks(mut self) -> Builder {
+        self.no_hooks = true;
+        self
+    }
+
     /// Spawns a new thread by taking ownership of the `Builder`, and returns an
     /// [`io::Result`] to its [`JoinHandle`].
     ///
@@ -460,7 +477,7 @@ impl Builder {
         F: Send,
         T: Send,
     {
-        let Builder { name, stack_size } = self;
+        let Builder { name, stack_size, no_hooks } = self;
 
         let stack_size = stack_size.unwrap_or_else(|| {
             static MIN: AtomicUsize = AtomicUsize::new(0);
@@ -485,6 +502,13 @@ impl Builder {
             Some(name) => Thread::new(id, name.into()),
             None => Thread::new_unnamed(id),
         };
+
+        let hooks = if no_hooks {
+            spawnhook::ChildSpawnHooks::default()
+        } else {
+            spawnhook::run_spawn_hooks(&my_thread)
+        };
+
         let their_thread = my_thread.clone();
 
         let my_packet: Arc<Packet<'scope, T>> = Arc::new(Packet {
@@ -494,9 +518,6 @@ impl Builder {
         });
         let their_packet = my_packet.clone();
 
-        let output_capture = crate::io::set_output_capture(None);
-        crate::io::set_output_capture(output_capture.clone());
-
         // Pass `f` in `MaybeUninit` because actually that closure might *run longer than the lifetime of `F`*.
         // See <https://github.com/rust-lang/rust/issues/101983> for more details.
         // To prevent leaks we use a wrapper that drops its contents.
@@ -534,10 +555,9 @@ impl Builder {
                 imp::Thread::set_name(name);
             }
 
-            crate::io::set_output_capture(output_capture);
-
             let f = f.into_inner();
             let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
+                crate::sys::backtrace::__rust_begin_short_backtrace(|| hooks.run());
                 crate::sys::backtrace::__rust_begin_short_backtrace(f)
             }));
             // SAFETY: `their_packet` as been built just above and moved by the
@@ -1126,9 +1146,9 @@ pub fn park_timeout_ms(ms: u32) {
 #[stable(feature = "park_timeout", since = "1.4.0")]
 pub fn park_timeout(dur: Duration) {
     let guard = PanicGuard;
-    // SAFETY: park_timeout is called on the parker owned by this thread.
+    // SAFETY: park_timeout is called on a handle owned by this thread.
     unsafe {
-        current().0.parker().park_timeout(dur);
+        current().park_timeout(dur);
     }
     // No panic occurred, do not abort.
     forget(guard);
@@ -1426,6 +1446,15 @@ impl Thread {
         unsafe { self.0.parker().park() }
     }
 
+    /// Like the public [`park_timeout`], but callable on any handle. This is
+    /// used to allow parking in TLS destructors.
+    ///
+    /// # Safety
+    /// May only be called from the thread to which this handle belongs.
+    pub(crate) unsafe fn park_timeout(&self, dur: Duration) {
+        unsafe { self.0.parker().park_timeout(dur) }
+    }
+
     /// Atomically makes the handle's token available if it is not already.
     ///
     /// Every thread is equipped with some basic low-level blocking support, via
diff --git a/library/std/src/thread/scoped.rs b/library/std/src/thread/scoped.rs
index b2305b1eda7..0033fc3a732 100644
--- a/library/std/src/thread/scoped.rs
+++ b/library/std/src/thread/scoped.rs
@@ -1,4 +1,4 @@
-use super::{Builder, JoinInner, Result, Thread, current, park};
+use super::{Builder, JoinInner, Result, Thread, current_or_unnamed};
 use crate::marker::PhantomData;
 use crate::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
 use crate::sync::Arc;
@@ -140,7 +140,7 @@ where
     let scope = Scope {
         data: Arc::new(ScopeData {
             num_running_threads: AtomicUsize::new(0),
-            main_thread: current(),
+            main_thread: current_or_unnamed(),
             a_thread_panicked: AtomicBool::new(false),
         }),
         env: PhantomData,
@@ -152,7 +152,8 @@ where
 
     // Wait until all the threads are finished.
     while scope.data.num_running_threads.load(Ordering::Acquire) != 0 {
-        park();
+        // SAFETY: this is the main thread, the handle belongs to us.
+        unsafe { scope.data.main_thread.park() };
     }
 
     // Throw any panic from `f`, or the return value of `f` if no thread panicked.
@@ -176,7 +177,7 @@ impl<'scope, 'env> Scope<'scope, 'env> {
     /// thread. If the spawned thread panics, [`join`] will return an [`Err`] containing
     /// the panic payload.
     ///
-    /// If the join handle is dropped, the spawned thread will implicitly joined at the
+    /// If the join handle is dropped, the spawned thread will be implicitly joined at the
     /// end of the scope. In that case, if the spawned thread panics, [`scope`] will
     /// panic after all threads are joined.
     ///
diff --git a/library/std/src/thread/spawnhook.rs b/library/std/src/thread/spawnhook.rs
new file mode 100644
index 00000000000..99b5ad9cb9f
--- /dev/null
+++ b/library/std/src/thread/spawnhook.rs
@@ -0,0 +1,148 @@
+use crate::cell::Cell;
+use crate::iter;
+use crate::sync::Arc;
+use crate::thread::Thread;
+
+crate::thread_local! {
+    /// A thread local linked list of spawn hooks.
+    ///
+    /// It is a linked list of Arcs, such that it can very cheaply be inhereted by spawned threads.
+    ///
+    /// (That technically makes it a set of linked lists with shared tails, so a linked tree.)
+    static SPAWN_HOOKS: Cell<SpawnHooks> = const { Cell::new(SpawnHooks { first: None }) };
+}
+
+#[derive(Default, Clone)]
+struct SpawnHooks {
+    first: Option<Arc<SpawnHook>>,
+}
+
+// Manually implement drop to prevent deep recursion when dropping linked Arc list.
+impl Drop for SpawnHooks {
+    fn drop(&mut self) {
+        let mut next = self.first.take();
+        while let Some(SpawnHook { hook, next: n }) = next.and_then(|n| Arc::into_inner(n)) {
+            drop(hook);
+            next = n;
+        }
+    }
+}
+
+struct SpawnHook {
+    hook: Box<dyn Send + Sync + Fn(&Thread) -> Box<dyn Send + FnOnce()>>,
+    next: Option<Arc<SpawnHook>>,
+}
+
+/// Registers a function to run for every newly thread spawned.
+///
+/// The hook is executed in the parent thread, and returns a function
+/// that will be executed in the new thread.
+///
+/// The hook is called with the `Thread` handle for the new thread.
+///
+/// The hook will only be added for the current thread and is inherited by the threads it spawns.
+/// In other words, adding a hook has no effect on already running threads (other than the current
+/// thread) and the threads they might spawn in the future.
+///
+/// Hooks can only be added, not removed.
+///
+/// The hooks will run in reverse order, starting with the most recently added.
+///
+/// # Usage
+///
+/// ```
+/// #![feature(thread_spawn_hook)]
+///
+/// std::thread::add_spawn_hook(|_| {
+///     ..; // This will run in the parent (spawning) thread.
+///     move || {
+///         ..; // This will run it the child (spawned) thread.
+///     }
+/// });
+/// ```
+///
+/// # Example
+///
+/// A spawn hook can be used to "inherit" a thread local from the parent thread:
+///
+/// ```
+/// #![feature(thread_spawn_hook)]
+///
+/// use std::cell::Cell;
+///
+/// thread_local! {
+///     static X: Cell<u32> = Cell::new(0);
+/// }
+///
+/// // This needs to be done once in the main thread before spawning any threads.
+/// std::thread::add_spawn_hook(|_| {
+///     // Get the value of X in the spawning thread.
+///     let value = X.get();
+///     // Set the value of X in the newly spawned thread.
+///     move || X.set(value)
+/// });
+///
+/// X.set(123);
+///
+/// std::thread::spawn(|| {
+///     assert_eq!(X.get(), 123);
+/// }).join().unwrap();
+/// ```
+#[unstable(feature = "thread_spawn_hook", issue = "132951")]
+pub fn add_spawn_hook<F, G>(hook: F)
+where
+    F: 'static + Send + Sync + Fn(&Thread) -> G,
+    G: 'static + Send + FnOnce(),
+{
+    SPAWN_HOOKS.with(|h| {
+        let mut hooks = h.take();
+        let next = hooks.first.take();
+        hooks.first = Some(Arc::new(SpawnHook {
+            hook: Box::new(move |thread| Box::new(hook(thread))),
+            next,
+        }));
+        h.set(hooks);
+    });
+}
+
+/// Runs all the spawn hooks.
+///
+/// Called on the parent thread.
+///
+/// Returns the functions to be called on the newly spawned thread.
+pub(super) fn run_spawn_hooks(thread: &Thread) -> ChildSpawnHooks {
+    // Get a snapshot of the spawn hooks.
+    // (Increments the refcount to the first node.)
+    let hooks = SPAWN_HOOKS.with(|hooks| {
+        let snapshot = hooks.take();
+        hooks.set(snapshot.clone());
+        snapshot
+    });
+    // Iterate over the hooks, run them, and collect the results in a vector.
+    let to_run: Vec<_> = iter::successors(hooks.first.as_deref(), |hook| hook.next.as_deref())
+        .map(|hook| (hook.hook)(thread))
+        .collect();
+    // Pass on the snapshot of the hooks and the results to the new thread,
+    // which will then run SpawnHookResults::run().
+    ChildSpawnHooks { hooks, to_run }
+}
+
+/// The results of running the spawn hooks.
+///
+/// This struct is sent to the new thread.
+/// It contains the inherited hooks and the closures to be run.
+#[derive(Default)]
+pub(super) struct ChildSpawnHooks {
+    hooks: SpawnHooks,
+    to_run: Vec<Box<dyn FnOnce() + Send>>,
+}
+
+impl ChildSpawnHooks {
+    // This is run on the newly spawned thread, directly at the start.
+    pub(super) fn run(self) {
+        SPAWN_HOOKS.set(self.hooks);
+        for run in self.to_run {
+            run();
+        }
+    }
+}