Skip to main content

commonware_parallel/rayon/
thread_pool.rs

1//! Contains support for user-managed thread pools, represented by the
2//! the [`ThreadPool`] type (see that struct for details).
3
4#[cfg(test)]
5use super::registry::WorkerThread;
6use super::{
7    ThreadPoolBuildError, ThreadPoolBuilder,
8    broadcast::{self, BroadcastContext},
9    latch::OnceLatch,
10    registry::Registry,
11    spawn,
12};
13use std::{
14    fmt,
15    future::Future,
16    pin::Pin,
17    sync::Arc,
18    task::{Context, Poll, Wake, Waker},
19};
20
21#[cfg(test)]
22mod future_test;
23#[cfg(test)]
24mod test;
25
26/// Represents a user-created [thread pool].
27///
28/// Use a [`ThreadPoolBuilder`] to specify the number and/or names of threads
29/// in the pool. After calling [`ThreadPoolBuilder::build()`], you can then
30/// execute functions explicitly within this [`ThreadPool`] using
31/// [`ThreadPool::install()`].
32///
33/// [`install()`][`ThreadPool::install()`] executes a closure in one of the `ThreadPool`'s
34/// threads. In addition, any other scheduler operations called inside of `install()` will also
35/// execute in the context of the `ThreadPool`.
36///
37/// When the `ThreadPool` is dropped, that's a signal for the threads it manages to terminate,
38/// they will complete executing any remaining work that you have spawned, and automatically
39/// terminate.
40///
41///
42/// [thread pool]: https://en.wikipedia.org/wiki/Thread_pool
43/// [`ThreadPoolBuilder::build()`]: ThreadPoolBuilder::build()
44/// [`ThreadPool::install()`]: Self::install()
45pub struct ThreadPool {
46    registry: Arc<Registry>,
47}
48
49struct WorkerWake {
50    latch: OnceLatch,
51    registry: Arc<Registry>,
52    index: usize,
53}
54
55impl Wake for WorkerWake {
56    fn wake(self: Arc<Self>) {
57        self.wake_by_ref();
58    }
59
60    fn wake_by_ref(self: &Arc<Self>) {
61        // SAFETY: this Arc keeps the latch and registry alive through the notification.
62        // The worker that created this waker is the only thread that waits on its latch.
63        unsafe { OnceLatch::set_and_tickle_one(&self.latch, &self.registry, self.index) };
64    }
65}
66
67impl ThreadPool {
68    pub(super) fn build(builder: ThreadPoolBuilder) -> Result<Self, ThreadPoolBuildError> {
69        let registry = Registry::new(builder)?;
70        Ok(Self { registry })
71    }
72
73    /// Executes `op` within the thread pool. Any attempts to use
74    /// `join` will then operate within that thread pool.
75    ///
76    /// # Warning: thread-local data
77    ///
78    /// Because `op` is executing within the thread pool,
79    /// thread-local data from the current thread will not be
80    /// accessible.
81    ///
82    /// # Warning: execution order
83    ///
84    /// If the current thread is part of a different thread pool, it will try to
85    /// keep busy while the `op` completes in its target pool, similar to
86    /// calling [`ThreadPool::yield_now()`] in a loop. Therefore, it may
87    /// potentially schedule other tasks to run on the current thread in the
88    /// meantime.
89    ///
90    /// # Panics
91    ///
92    /// If `op` should panic, that panic will be propagated.
93    pub fn install<OP, R>(&self, op: OP) -> R
94    where
95        OP: FnOnce() -> R + Send,
96        R: Send,
97    {
98        self.registry.in_worker(|_, _| op())
99    }
100
101    /// Executes `op` within every thread in the thread pool. Any attempts to use
102    /// `join` will then operate within that thread pool.
103    ///
104    /// Broadcasts are executed on each thread after they have exhausted their
105    /// local work queue, before they attempt work-stealing from other threads.
106    /// The goal of that strategy is to run everywhere in a timely manner
107    /// *without* being too disruptive to current work.
108    ///
109    /// # Warning: thread-local data
110    ///
111    /// Because `op` is executing within the thread pool,
112    /// thread-local data from the current thread will not be
113    /// accessible.
114    ///
115    /// # Panics
116    ///
117    /// If `op` should panic on one or more threads, exactly one panic
118    /// will be propagated, only after all threads have completed
119    /// (or panicked) their own `op`.
120    pub fn broadcast<OP, R>(&self, op: OP) -> Vec<R>
121    where
122        OP: Fn(BroadcastContext<'_>) -> R + Sync,
123        R: Send,
124    {
125        // SAFETY: `self` holds a reference that keeps the registry from terminating.
126        unsafe { broadcast::broadcast_in(op, &self.registry) }
127    }
128
129    /// Returns the number of threads in the thread pool.
130    #[inline]
131    pub fn current_num_threads(&self) -> usize {
132        self.registry.num_threads()
133    }
134
135    /// If called from a worker thread in this thread pool,
136    /// returns the index of that thread; if not called from a worker
137    /// thread, or called from a worker that belongs to a
138    /// different thread pool, returns `None`.
139    ///
140    /// The index for a given thread will not change over the thread's
141    /// lifetime. However, multiple threads may share the same index if
142    /// they are in distinct thread pools.
143    #[inline]
144    pub fn current_thread_index(&self) -> Option<usize> {
145        let curr = self.registry.current_thread()?;
146        Some(curr.index())
147    }
148
149    /// Polls `future` to completion while the calling worker services this pool's jobs.
150    /// Returns `None` without polling if the caller does not belong to this pool.
151    ///
152    /// Jobs run on the caller's stack, so blocking waits within them must allow nesting.
153    pub(crate) fn block_on_worker<F: Future + ?Sized>(
154        &self,
155        mut future: Pin<&mut F>,
156    ) -> Option<F::Output> {
157        let worker = self.registry.current_thread()?;
158        loop {
159            // A future may retain a waker after it is polled again or completes.
160            // Each poll owns its latch so those wakers remain valid independently.
161            let wake = Arc::new(WorkerWake {
162                latch: OnceLatch::new(),
163                registry: Arc::clone(worker.registry()),
164                index: worker.index(),
165            });
166            let waker = Waker::from(Arc::clone(&wake));
167            match future.as_mut().poll(&mut Context::from_waker(&waker)) {
168                Poll::Ready(result) => return Some(result),
169                Poll::Pending => {
170                    // SAFETY: this is the calling worker, and the local Arc keeps its
171                    // latch alive until the scheduler returns from the wait.
172                    unsafe { worker.wait_until(&wake.latch) };
173                }
174            }
175        }
176    }
177
178    /// Spawns an asynchronous task in this thread pool. This task may
179    /// outlast the current stack frame, so it cannot capture any references
180    /// onto the stack (you will likely need a `move` closure).
181    ///
182    /// # Panics
183    ///
184    /// If the task panics, the process aborts, since the panic has nowhere to propagate.
185    pub fn spawn<OP>(&self, op: OP)
186    where
187        OP: FnOnce() + Send + 'static,
188    {
189        // SAFETY: `self` holds a reference that keeps the registry from terminating.
190        unsafe { spawn::spawn_in(op, &self.registry) }
191    }
192
193    /// Spawns an asynchronous task on every thread in this thread pool. This task
194    /// may outlast the current stack frame, so it cannot capture any references
195    /// onto the stack (you will likely need a `move` closure).
196    ///
197    /// # Panics
198    ///
199    /// If the task panics on any thread, the process aborts, since the panic has nowhere to
200    /// propagate.
201    pub fn spawn_broadcast<OP>(&self, op: OP)
202    where
203        OP: Fn(BroadcastContext<'_>) + Send + Sync + 'static,
204    {
205        // SAFETY: `self` holds a reference that keeps the registry from terminating.
206        unsafe { broadcast::spawn_broadcast_in(op, &self.registry) }
207    }
208
209    /// Cooperatively yields execution to the scheduler.
210    ///
211    /// Looks for and executes one unit of work if the current thread is part of this pool.
212    ///
213    /// Returns `Some(Yield::Executed)` if anything was executed, `Some(Yield::Idle)` if
214    /// nothing was available, or `None` if the current thread is not part of this pool.
215    pub fn yield_now(&self) -> Option<Yield> {
216        let curr = self.registry.current_thread()?;
217        Some(curr.yield_now())
218    }
219}
220
221impl Drop for ThreadPool {
222    fn drop(&mut self) {
223        self.registry.terminate();
224    }
225}
226
227impl fmt::Debug for ThreadPool {
228    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
229        fmt.debug_struct("ThreadPool")
230            .field("num_threads", &self.current_num_threads())
231            .field("id", &self.registry.id())
232            .finish()
233    }
234}
235
236/// If called from a worker thread, returns the index of that
237/// thread within its current pool; if not called from a worker thread,
238/// returns `None`.
239///
240/// The index for a given thread will not change over the thread's
241/// lifetime. However, multiple threads may share the same index if
242/// they are in distinct thread pools.
243///
244/// See also: [the `ThreadPool::current_thread_index()` method][m].
245///
246/// [m]: ThreadPool::current_thread_index()
247#[cfg(test)]
248#[inline]
249pub(crate) fn current_thread_index() -> Option<usize> {
250    // SAFETY: a non-null pointer is this thread's worker state, which outlives the call.
251    let curr = unsafe { WorkerThread::current().as_ref()? };
252    Some(curr.index())
253}
254
255/// Cooperatively yields execution to the scheduler.
256///
257/// If the current thread is part of a thread pool, this looks for a
258/// single unit of pending work in the pool, then executes it. Completion of
259/// that work might include nested work or further work stealing.
260///
261/// This is similar to [`std::thread::yield_now()`], but does not literally make
262/// that call. If you are implementing a polling loop, you may want to also
263/// yield to the OS scheduler yourself if no work was found.
264///
265/// Returns `Some(Yield::Executed)` if anything was executed, `Some(Yield::Idle)` if
266/// nothing was available, or `None` if this thread is not part of any pool at all.
267#[cfg(test)]
268pub(crate) fn yield_now() -> Option<Yield> {
269    // SAFETY: a non-null pointer is this thread's worker state, which outlives the call.
270    let thread = unsafe { WorkerThread::current().as_ref()? };
271    Some(thread.yield_now())
272}
273
274/// Result of a worker's cooperative yield to its thread pool.
275#[derive(Clone, Copy, Debug, PartialEq, Eq)]
276pub enum Yield {
277    /// Work was found and executed.
278    Executed,
279    /// No available work was found.
280    Idle,
281}