Skip to main content

commonware_parallel/rayon/
mod.rs

1//! A work-stealing pool of worker threads.
2//!
3//! A [`ThreadPool`] owns a fixed set of worker threads. Each worker keeps a deque of jobs that it
4//! pushes and pops at one end while idle workers steal from the other, and a pool-wide injector
5//! takes jobs submitted from threads outside the pool. A worker with nothing to run searches for
6//! work for a while and then sleeps. Publishing a job or setting a latch wakes sleeping workers
7//! as needed. A worker waiting for a job's latch keeps running its pool's jobs until it is set.
8//!
9//! Internal `join` calls run two closures potentially in parallel. [`ThreadPool::spawn`] runs a
10//! detached task, and [`ThreadPool::broadcast`] and [`ThreadPool::spawn_broadcast`] run a task on
11//! every worker. The bridge maps, folds, and collects vectors by splitting them adaptively.
12//! Sorting uses `par_mergesort` for stable sorts and `par_quicksort` for unstable sorts.
13//!
14//! # Changes vs. Upstream
15//!
16//! This module vendors the scheduler of [rayon-core] 1.13.0 and the producer/consumer bridge and
17//! slice sorts of [rayon] 1.12.0. The upstream `LICENSE-APACHE`, `LICENSE-MIT`, and sleep protocol
18//! `README.md` accompany the code.
19//!
20//! - Moved rayon-core into this module and rewrote crate paths. Flattened the `broadcast`, `join`,
21//!   `spawn`, and `thread_pool` modules beside their test directories.
22//! - Exports `ThreadPool`, `ThreadPoolBuilder`, `ThreadPoolBuildError`, and `Yield` through
23//!   `commonware_parallel::rayon`. Retained free functions are crate-private or test-only.
24//! - Removed the global pool, `build_global`, `initialize`, `Registry::current`, the `primed`
25//!   latch, and `GlobalPoolAlreadyInitialized`. Internal `join` and `current_num_threads` require a
26//!   calling worker.
27//! - Removed free `spawn`, `broadcast`, and `spawn_broadcast`.
28//! - Removed scopes and their `CountLatch` operations, FIFO scheduling, `yield_local`,
29//!   `current_thread_has_pending_tasks`, `max_num_threads`, and `ThreadPool::join`.
30//! - Removed `Configuration`, `ThreadPool::new`, and the deprecated error description override.
31//! - Removed builder options `spawn_handler`, `build_scoped`, `exit_handler`, `panic_handler`, and
32//!   `breadth_first`. The registry starts threads directly. Uncaught detached-job and start-handler
33//!   panics abort.
34//! - Removed the fallback for targets without threads, `ThreadPoolBuildError::is_unsupported`,
35//!   `web_spin_lock`, `wasm_sync`, the build script, and the `links` key.
36//! - `bridge.rs` directly composes the producer and consumers. Removed iterator traits and
37//!   wrappers, `ProducerCallback`, producer-side map adapters, unindexed plumbing, ranged draining,
38//!   and `NoopConsumer`. Whole-vector draining uses inherent methods and `IntoIterator`. Fallible
39//!   folds and reductions use `Result` in place of `Try` and `ControlFlow`.
40//! - Merge-sort chunks run through recursive `join` calls in the same indexed split order as the
41//!   upstream `par_chunks_mut` iterator.
42//! - Failed collections drop partial outputs before returning the error, without assembling a final
43//!   vector.
44//! - Added `ThreadPool::block_on_worker` to poll futures while servicing pool jobs and waiting
45//!   through the scheduler's latch and sleep protocol.
46//! - Added `ThreadPoolBuilder::unstarted_workers` for test mocks and a test-only notification when
47//!   a worker commits to sleeping.
48//! - Tests use explicit pools and `commonware_utils::test_rng`. Removed compile-fail tests and
49//!   tests of omitted APIs. Added worker, collection, sorting, and wakeup regressions.
50//! - Adapted unsafe blocks, borrowing, and syntax to Rust 2024 and workspace lints. Added `SAFETY`
51//!   comments and local lint allowances for upstream `std::sync` primitives. List collection uses
52//!   `Vec::with_capacity` in place of `new` followed by `reserve`.
53//! - Adapted docs to the retained API, clarified queued join execution, removed inaccessible
54//!   doctests and section banners, and named the scheduler in its abort message.
55//!
56//! [rayon-core]: https://crates.io/crates/rayon-core/1.13.0
57//! [rayon]: https://crates.io/crates/rayon/1.12.0
58
59use std::{env, error::Error, fmt, io, str::FromStr, thread};
60
61mod bridge;
62mod broadcast;
63mod job;
64mod join;
65mod latch;
66mod registry;
67mod sleep;
68mod sort;
69mod spawn;
70#[cfg(test)]
71mod test;
72mod thread_pool;
73mod unwind;
74
75pub use self::thread_pool::{ThreadPool, Yield};
76#[cfg(test)]
77pub(crate) use self::thread_pool::{current_thread_index, yield_now};
78pub(crate) use self::{
79    bridge::{fold_init, map_collect_vec, map_init_collect_vec, try_fold, try_map_collect_vec},
80    join::join,
81    sort::{par_mergesort, par_quicksort},
82};
83
84/// Returns the number of threads in the pool of the calling worker.
85///
86/// # Panics
87///
88/// Panics if the calling thread is not a worker of a thread pool.
89pub(crate) fn current_num_threads() -> usize {
90    registry::Registry::current_num_threads()
91}
92
93/// We need to transmit raw pointers across threads. It is possible to do this
94/// without any unsafe code by converting pointers to usize or to `AtomicPtr<T>`
95/// then back to a raw pointer for use. We prefer this approach because code
96/// that uses this type is more explicit.
97///
98/// Unsafe code is still required to dereference the pointer, so this type is
99/// not unsound on its own, although it does partly lift the unconditional
100/// !Send and !Sync on raw pointers. As always, dereference with care.
101struct SendPtr<T>(*mut T);
102
103// SAFETY: !Send for raw pointers is not for safety, just as a lint
104unsafe impl<T: Send> Send for SendPtr<T> {}
105
106// SAFETY: !Sync for raw pointers is not for safety, just as a lint
107unsafe impl<T: Send> Sync for SendPtr<T> {}
108
109impl<T> SendPtr<T> {
110    // Helper to avoid disjoint captures of `send_ptr.0`
111    const fn get(self) -> *mut T {
112        self.0
113    }
114}
115
116// Implement Clone without the T: Clone bound from the derive
117impl<T> Clone for SendPtr<T> {
118    fn clone(&self) -> Self {
119        *self
120    }
121}
122
123// Implement Copy without the T: Copy bound from the derive
124impl<T> Copy for SendPtr<T> {}
125
126/// Error when initializing a thread pool.
127#[derive(Debug)]
128pub struct ThreadPoolBuildError {
129    kind: ErrorKind,
130}
131
132#[derive(Debug)]
133enum ErrorKind {
134    CurrentThreadAlreadyInPool,
135    IOError(io::Error),
136}
137
138/// Used to create a new [`ThreadPool`].
139///
140/// # Examples
141///
142/// The following creates a thread pool with 4 threads.
143///
144/// ```
145/// use commonware_parallel::rayon::ThreadPoolBuilder;
146///
147/// let pool = ThreadPoolBuilder::new().num_threads(4).build().unwrap();
148/// assert_eq!(pool.current_num_threads(), 4);
149/// assert_eq!(pool.install(|| 1 + 1), 2);
150/// ```
151pub struct ThreadPoolBuilder {
152    /// The number of threads in the thread pool.
153    /// If zero, use the environment or the number of logical CPUs.
154    num_threads: usize,
155
156    /// The thread we're building *from* will also be part of the pool.
157    use_current_thread: bool,
158
159    /// Closure to compute the name of a thread.
160    get_thread_name: Option<Box<dyn FnMut(usize) -> String>>,
161
162    /// The stack size for the created worker threads
163    stack_size: Option<usize>,
164
165    /// Closure invoked on worker-thread start.
166    start_handler: Option<Box<StartHandler>>,
167
168    /// Whether the pool starts a thread for each worker it does not take over.
169    start_threads: bool,
170}
171
172/// The type for a closure that gets invoked when a thread starts. The
173/// closure is passed the index of the thread on which it is invoked.
174/// Note that this same closure may be invoked multiple times in parallel.
175type StartHandler = dyn Fn(usize) + Send + Sync;
176
177impl Default for ThreadPoolBuilder {
178    fn default() -> Self {
179        Self {
180            num_threads: 0,
181            use_current_thread: false,
182            get_thread_name: None,
183            stack_size: None,
184            start_handler: None,
185            start_threads: true,
186        }
187    }
188}
189
190impl ThreadPoolBuilder {
191    /// Creates and returns a valid thread pool builder, but does not initialize it.
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    /// Creates a new `ThreadPool` initialized using this configuration.
197    pub fn build(self) -> Result<ThreadPool, ThreadPoolBuildError> {
198        ThreadPool::build(self)
199    }
200
201    /// Get the number of threads that will be used for the thread
202    /// pool. See `num_threads()` for more information.
203    fn get_num_threads(&self) -> usize {
204        if self.num_threads > 0 {
205            self.num_threads
206        } else {
207            let default = || {
208                thread::available_parallelism()
209                    .map(|n| n.get())
210                    .unwrap_or(1)
211            };
212
213            match env::var("RAYON_NUM_THREADS")
214                .ok()
215                .and_then(|s| usize::from_str(&s).ok())
216            {
217                Some(x @ 1..) => return x,
218                Some(0) => return default(),
219                _ => {}
220            }
221
222            // Support for deprecated `RAYON_RS_NUM_CPUS`.
223            match env::var("RAYON_RS_NUM_CPUS")
224                .ok()
225                .and_then(|s| usize::from_str(&s).ok())
226            {
227                Some(x @ 1..) => x,
228                _ => default(),
229            }
230        }
231    }
232
233    /// Get the thread name for the thread with the given index.
234    fn get_thread_name(&mut self, index: usize) -> Option<String> {
235        let f = self.get_thread_name.as_mut()?;
236        Some(f(index))
237    }
238
239    /// Sets a closure which takes a thread index and returns
240    /// the thread's name.
241    pub fn thread_name<F>(mut self, closure: F) -> Self
242    where
243        F: FnMut(usize) -> String + 'static,
244    {
245        self.get_thread_name = Some(Box::new(closure));
246        self
247    }
248
249    /// Sets the number of threads to be used in the thread pool.
250    ///
251    /// If you specify a non-zero number of threads using this
252    /// function, then the resulting thread pools are guaranteed to
253    /// start at most this number of threads.
254    ///
255    /// If `num_threads` is 0, or you do not call this function, then
256    /// the pool uses `RAYON_NUM_THREADS`, then the deprecated `RAYON_RS_NUM_CPUS`, and
257    /// otherwise the number of logical CPUs reported by [`std::thread::available_parallelism`].
258    /// A zero `RAYON_NUM_THREADS` selects the logical CPU count.
259    pub const fn num_threads(mut self, num_threads: usize) -> Self {
260        self.num_threads = num_threads;
261        self
262    }
263
264    /// Use the current thread as one of the threads in the pool.
265    ///
266    /// The current thread is guaranteed to be at index 0, and since the thread is not managed by
267    /// the pool, the start handler does not run for that thread.
268    ///
269    /// Note that the current thread won't run the main work-stealing loop, so jobs spawned into
270    /// the thread pool will generally not be picked up automatically by this thread unless you
271    /// yield to the pool in some way, like via the pool's `yield_now`.
272    ///
273    /// # Leaks
274    ///
275    /// The current thread keeps its worker state for the rest of its life, so the pool's registry
276    /// is leaked.
277    pub const fn use_current_thread(mut self) -> Self {
278        self.use_current_thread = true;
279        self
280    }
281
282    /// Get the stack size of the worker threads
283    const fn get_stack_size(&self) -> Option<usize> {
284        self.stack_size
285    }
286
287    /// Sets the stack size of the worker threads
288    pub const fn stack_size(mut self, stack_size: usize) -> Self {
289        self.stack_size = Some(stack_size);
290        self
291    }
292
293    /// Takes the current thread start callback, leaving `None`.
294    const fn take_start_handler(&mut self) -> Option<Box<StartHandler>> {
295        self.start_handler.take()
296    }
297
298    /// Sets a callback to be invoked on thread start.
299    ///
300    /// The closure is passed the index of the thread on which it is invoked.
301    /// Note that this same closure may be invoked multiple times in parallel.
302    /// If this closure panics, the process aborts.
303    pub fn start_handler<H>(mut self, start_handler: H) -> Self
304    where
305        H: Fn(usize) + Send + Sync + 'static,
306    {
307        self.start_handler = Some(Box::new(start_handler));
308        self
309    }
310
311    /// Registers the pool's workers without starting their threads, so jobs queued on the pool
312    /// run only on a thread taken over with [`Self::use_current_thread`], or never.
313    #[cfg(any(test, feature = "test-utils"))]
314    pub(crate) const fn unstarted_workers(mut self) -> Self {
315        self.start_threads = false;
316        self
317    }
318}
319
320impl ThreadPoolBuildError {
321    const fn new(kind: ErrorKind) -> Self {
322        Self { kind }
323    }
324}
325
326const CURRENT_THREAD_ALREADY_IN_POOL: &str =
327    "The current thread is already part of another thread pool.";
328
329impl Error for ThreadPoolBuildError {
330    fn source(&self) -> Option<&(dyn Error + 'static)> {
331        match &self.kind {
332            ErrorKind::CurrentThreadAlreadyInPool => None,
333            ErrorKind::IOError(e) => Some(e),
334        }
335    }
336}
337
338impl fmt::Display for ThreadPoolBuildError {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        match &self.kind {
341            ErrorKind::CurrentThreadAlreadyInPool => CURRENT_THREAD_ALREADY_IN_POOL.fmt(f),
342            ErrorKind::IOError(e) => e.fmt(f),
343        }
344    }
345}
346
347impl fmt::Debug for ThreadPoolBuilder {
348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349        let Self {
350            ref num_threads,
351            ref use_current_thread,
352            ref get_thread_name,
353            ref stack_size,
354            ref start_handler,
355            ref start_threads,
356        } = *self;
357
358        // Just print `Some(<closure>)` or `None` to the debug
359        // output.
360        struct ClosurePlaceholder;
361        impl fmt::Debug for ClosurePlaceholder {
362            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363                f.write_str("<closure>")
364            }
365        }
366        let get_thread_name = get_thread_name.as_ref().map(|_| ClosurePlaceholder);
367        let start_handler = start_handler.as_ref().map(|_| ClosurePlaceholder);
368
369        f.debug_struct("ThreadPoolBuilder")
370            .field("num_threads", num_threads)
371            .field("use_current_thread", use_current_thread)
372            .field("get_thread_name", &get_thread_name)
373            .field("stack_size", &stack_size)
374            .field("start_handler", &start_handler)
375            .field("start_threads", start_threads)
376            .finish()
377    }
378}