stdx/
thread.rs

1//! A utility module for working with threads that automatically joins threads upon drop
2//! and abstracts over operating system quality of service (`QoS`) APIs
3//! through the concept of a “thread intent”.
4//!
5//! The intent of a thread is frozen at thread creation time,
6//! i.e. there is no API to change the intent of a thread once it has been spawned.
7//!
8//! As a system, rust-analyzer should have the property that
9//! old manual scheduling APIs are replaced entirely by `QoS`.
10//! To maintain this invariant, we panic when it is clear that
11//! old scheduling APIs have been used.
12//!
13//! Moreover, we also want to ensure that every thread has an intent set explicitly
14//! to force a decision about its importance to the system.
15//! Thus, [`ThreadIntent`] has no default value
16//! and every entry point to creating a thread requires a [`ThreadIntent`] upfront.
17
18use std::fmt;
19
20mod intent;
21mod pool;
22
23pub use intent::ThreadIntent;
24pub use pool::Pool;
25
26/// # Panics
27///
28/// Panics if failed to spawn the thread.
29pub fn spawn<F, T>(intent: ThreadIntent, name: String, f: F) -> JoinHandle<T>
30where
31    F: (FnOnce() -> T) + Send + 'static,
32    T: Send + 'static,
33{
34    Builder::new(intent, name).spawn(f).expect("failed to spawn thread")
35}
36
37pub struct Builder {
38    intent: ThreadIntent,
39    inner: jod_thread::Builder,
40    allow_leak: bool,
41}
42
43impl Builder {
44    #[must_use]
45    pub fn new(intent: ThreadIntent, name: impl Into<String>) -> Self {
46        Self { intent, inner: jod_thread::Builder::new().name(name.into()), allow_leak: false }
47    }
48
49    #[must_use]
50    pub fn stack_size(self, size: usize) -> Self {
51        Self { inner: self.inner.stack_size(size), ..self }
52    }
53
54    /// Whether dropping should detach the thread
55    /// instead of joining it.
56    #[must_use]
57    pub fn allow_leak(self, allow_leak: bool) -> Self {
58        Self { allow_leak, ..self }
59    }
60
61    pub fn spawn<F, T>(self, f: F) -> std::io::Result<JoinHandle<T>>
62    where
63        F: (FnOnce() -> T) + Send + 'static,
64        T: Send + 'static,
65    {
66        let inner_handle = self.inner.spawn(move || {
67            self.intent.apply_to_current_thread();
68            f()
69        })?;
70
71        Ok(JoinHandle { inner: Some(inner_handle), allow_leak: self.allow_leak })
72    }
73}
74
75pub struct JoinHandle<T = ()> {
76    // `inner` is an `Option` so that we can
77    // take ownership of the contained `JoinHandle`.
78    inner: Option<jod_thread::JoinHandle<T>>,
79    allow_leak: bool,
80}
81
82impl<T> JoinHandle<T> {
83    /// # Panics
84    ///
85    /// Panics if there is no thread to join.
86    #[must_use]
87    pub fn join(mut self) -> T {
88        self.inner.take().unwrap().join()
89    }
90}
91
92impl<T> Drop for JoinHandle<T> {
93    fn drop(&mut self) {
94        if !self.allow_leak {
95            return;
96        }
97
98        if let Some(join_handle) = self.inner.take() {
99            join_handle.detach();
100        }
101    }
102}
103
104#[expect(clippy::min_ident_chars, reason = "trait impl")]
105impl<T> fmt::Debug for JoinHandle<T> {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        f.pad("JoinHandle { .. }")
108    }
109}