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}