Skip to main content

commonware_parallel/
lib.rs

1//! Parallelize fold operations with pluggable execution strategies.
2//!
3//! This crate provides the [`Strategy`] trait, which abstracts over sequential and parallel
4//! execution of fold operations. This allows algorithms to be written once and executed either
5//! sequentially or in parallel depending on the chosen strategy.
6//!
7//! # Overview
8//!
9//! The core abstraction is the [`Strategy`] trait, which provides several operations:
10//!
11//! **Core Operations:**
12//! - [`fold`](Strategy::fold): Reduces a collection to a single value
13//! - [`try_fold`](Strategy::try_fold): Like `fold`, but stops applying the fold operation after
14//!   failures
15//! - [`fold_init`](Strategy::fold_init): Like `fold`, but with per-partition initialization
16//! - [`sort_by`](Strategy::sort_by): Sorts a slice with a comparator
17//!
18//! **Convenience Methods:**
19//! - [`map_collect_vec`](Strategy::map_collect_vec): Maps elements and collects into a `Vec`
20//! - [`try_map_collect_vec`](Strategy::try_map_collect_vec): Maps fallible operations and
21//!   collects into a `Result<Vec<_>, _>`
22//! - [`map_init_collect_vec`](Strategy::map_init_collect_vec): Like `map_collect_vec` with
23//!   per-partition initialization
24//! - [`map_partition_collect_vec`](Strategy::map_partition_collect_vec): Maps elements, collecting
25//!   successful results and tracking indices of filtered elements
26//!
27//! Two implementations are provided:
28//!
29//! - [`Sequential`]: Executes operations sequentially on the current thread (works in `no_std`)
30//! - [`Rayon`]: Adaptively executes collection operations serially or with a [`rayon`] thread pool
31//!   (requires `std`)
32//!
33//! # Features
34//!
35//! - `std` (default): Enables the [`Rayon`] strategy backed by rayon
36//!
37//! When the `std` feature is disabled, only [`Sequential`] is available, making this crate
38//! suitable for `no_std` environments.
39//!
40//! # Example
41//!
42//! The main benefit of this crate is writing algorithms that can switch between sequential
43//! and parallel execution:
44//!
45//! ```
46//! use commonware_parallel::{Strategy, Sequential};
47//!
48//! fn sum_of_squares(strategy: &impl Strategy, data: &[i64]) -> i64 {
49//!     strategy.fold(
50//!         data,
51//!         || 0i64,
52//!         |acc, &x| acc + x * x,
53//!         |a, b| a + b,
54//!     )
55//! }
56//!
57//! let strategy = Sequential;
58//! let data = vec![1, 2, 3, 4, 5];
59//! let result = sum_of_squares(&strategy, &data);
60//! assert_eq!(result, 55); // 1 + 4 + 9 + 16 + 25
61//! ```
62
63#![doc(
64    html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
65    html_favicon_url = "https://commonware.xyz/favicon.ico"
66)]
67#![cfg_attr(not(any(feature = "std", test)), no_std)]
68
69commonware_macros::stability_scope!(BETA {
70    use cfg_if::cfg_if;
71    use core::{cmp::Ordering, fmt, num::NonZeroUsize};
72
73    cfg_if! {
74        if #[cfg(any(feature = "std", test))] {
75            use core::convert::Infallible;
76            use futures::{
77                channel::oneshot,
78                future::{self, Either},
79            };
80            use rayon::{
81                iter::{IntoParallelIterator, ParallelIterator},
82                slice::ParallelSliceMut,
83                ThreadPool as RThreadPool, ThreadPoolBuildError, ThreadPoolBuilder, Yield,
84            };
85            use std::{
86                panic::{self, AssertUnwindSafe, Location},
87                sync::Arc,
88            };
89
90            mod policy;
91        } else {
92            extern crate alloc;
93            use alloc::vec::Vec;
94        }
95    }
96
97    /// A strategy wrapper for manually partitioned work.
98    ///
99    /// This disables adaptive serial-vs-parallel policy decisions for operations that callers have
100    /// already split into partitions.
101    #[derive(Clone, Debug)]
102    pub struct Manual<S> {
103        strategy: S,
104        parallelism: usize,
105    }
106
107    impl<S> Manual<S> {
108        /// Creates a strategy wrapper for manually partitioned work.
109        pub const fn new(strategy: S, parallelism: NonZeroUsize) -> Self {
110            Self {
111                strategy,
112                parallelism: parallelism.get(),
113            }
114        }
115
116        /// Returns the parallelism to use for manually partitioned work.
117        pub const fn parallelism(&self) -> usize {
118            self.parallelism
119        }
120    }
121
122    /// A strategy for executing fold operations.
123    ///
124    /// This trait abstracts over sequential and parallel execution, allowing algorithms
125    /// to be written generically and then executed with different strategies depending
126    /// on the use case (e.g., sequential for testing/debugging, parallel for production).
127    pub trait Strategy: Clone + Send + Sync + fmt::Debug + 'static {
128        /// Returns a strategy wrapper for manually partitioned work.
129        fn manual(&self) -> Manual<Self>
130        where
131            Self: Sized;
132
133        /// Submit one CPU-bound job to this strategy.
134        ///
135        /// The returned future resolves when the submitted job completes, but blocking on external
136        /// synchronization or I/O inside the job can occupy execution capacity until it returns.
137        /// When the polling thread itself belongs to the strategy's execution resources (e.g. a
138        /// runtime whose executor thread is registered as a pool worker), the job (and other
139        /// pending work) may be executed inline on that thread rather than waited on.
140        ///
141        /// If the job panics, the panic is propagated to the caller; it never aborts the process.
142        fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
143        where
144            F: FnOnce(Self) -> T + Send + 'static,
145            T: Send + 'static;
146
147        /// Runs either a serial or parallel body.
148        #[track_caller]
149        fn run<R, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> R
150        where
151            R: Send,
152            SEQ: FnOnce() -> R + Send,
153            PAR: FnOnce() -> R + Send;
154
155        /// Like [`run`](Self::run), but for fallible work.
156        ///
157        /// The strategy chooses and runs either the serial or parallel body, returning the
158        /// first error produced by the chosen body. Elapsed time is only recorded on success,
159        /// so abort-early error paths cannot poison the adaptive policy's estimates.
160        #[track_caller]
161        fn try_run<R, E, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> Result<R, E>
162        where
163            R: Send,
164            E: Send,
165            SEQ: FnOnce() -> Result<R, E> + Send,
166            PAR: FnOnce() -> Result<R, E> + Send;
167
168        /// Reduces a collection to a single value with per-partition initialization.
169        ///
170        /// Similar to [`fold`](Self::fold), but provides a separate initialization value
171        /// that is created once per partition. This is useful when the fold operation
172        /// requires mutable state that should not be shared across partitions (e.g., a
173        /// scratch buffer, RNG, or expensive-to-clone resource).
174        ///
175        /// # Arguments
176        ///
177        /// - `iter`: The collection to fold over
178        /// - `init`: Creates the per-partition initialization value
179        /// - `identity`: Creates the identity value for the accumulator
180        /// - `fold_op`: Combines accumulator with init state and item: `(acc, &mut init, item) -> acc`
181        /// - `reduce_op`: Combines two accumulators: `(acc1, acc2) -> acc`
182        ///
183        /// # Examples
184        ///
185        /// ```
186        /// use commonware_parallel::{Strategy, Sequential};
187        ///
188        /// let strategy = Sequential;
189        /// let data = vec![1u32, 2, 3, 4, 5];
190        ///
191        /// // Use a scratch buffer to avoid allocations in the inner loop
192        /// let result: Vec<String> = strategy.fold_init(
193        ///     &data,
194        ///     || String::with_capacity(16),  // Per-partition scratch buffer
195        ///     Vec::new,                       // Identity for accumulator
196        ///     |mut acc, buf, &n| {
197        ///         buf.clear();
198        ///         use std::fmt::Write;
199        ///         write!(buf, "num:{}", n).unwrap();
200        ///         acc.push(buf.clone());
201        ///         acc
202        ///     },
203        ///     |mut a, b| { a.extend(b); a },
204        /// );
205        ///
206        /// assert_eq!(result, vec!["num:1", "num:2", "num:3", "num:4", "num:5"]);
207        /// ```
208        #[track_caller]
209        fn fold_init<I, INIT, T, R, ID, F, RD>(
210            &self,
211            iter: I,
212            init: INIT,
213            identity: ID,
214            fold_op: F,
215            reduce_op: RD,
216        ) -> R
217        where
218            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
219            INIT: Fn() -> T + Send + Sync,
220            T: Send,
221            R: Send,
222            ID: Fn() -> R + Send + Sync,
223            F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
224            RD: Fn(R, R) -> R + Send + Sync;
225
226        /// Reduces a collection to a single value using fold and reduce operations.
227        ///
228        /// This method processes elements from the iterator, combining them into a single
229        /// result.
230        ///
231        /// # Arguments
232        ///
233        /// - `iter`: The collection to fold over
234        /// - `identity`: A closure that produces the identity value for the fold.
235        /// - `fold_op`: Combines an accumulator with a single item: `(acc, item) -> acc`
236        /// - `reduce_op`: Combines two accumulators: `(acc1, acc2) -> acc`.
237        ///
238        /// # Examples
239        ///
240        /// ## Sum of Elements
241        ///
242        /// ```
243        /// use commonware_parallel::{Strategy, Sequential};
244        ///
245        /// let strategy = Sequential;
246        /// let numbers = vec![1, 2, 3, 4, 5];
247        ///
248        /// let sum = strategy.fold(
249        ///     &numbers,
250        ///     || 0,                    // identity
251        ///     |acc, &n| acc + n,       // fold: add each number
252        ///     |a, b| a + b,            // reduce: combine partial sums
253        /// );
254        ///
255        /// assert_eq!(sum, 15);
256        /// ```
257        #[track_caller]
258        fn fold<I, R, ID, F, RD>(&self, iter: I, identity: ID, fold_op: F, reduce_op: RD) -> R
259        where
260            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
261            R: Send,
262            ID: Fn() -> R + Send + Sync,
263            F: Fn(R, I::Item) -> R + Send + Sync,
264            RD: Fn(R, R) -> R + Send + Sync,
265        {
266            self.fold_init(
267                iter,
268                || (),
269                identity,
270                |acc, _, item| fold_op(acc, item),
271                reduce_op,
272            )
273        }
274
275        /// Reduces a collection to a single value using a fallible fold operation.
276        ///
277        /// Similar to [`fold`](Self::fold), but `fold_op` may fail. Implementations may stop
278        /// applying `fold_op` after an error is observed. When more than one partition fails,
279        /// any error may be returned.
280        ///
281        /// Adaptive strategies must only record elapsed time when the fold succeeds, so
282        /// abort-early error paths cannot poison the policy's estimates.
283        ///
284        /// # Arguments
285        ///
286        /// - `iter`: The collection to fold over
287        /// - `identity`: A closure that produces the identity value for the fold.
288        /// - `fold_op`: Fallibly combines an accumulator with a single item: `(acc, item) -> Result<acc, E>`
289        /// - `reduce_op`: Combines two successful accumulators: `(acc1, acc2) -> acc`.
290        #[track_caller]
291        fn try_fold<I, R, E, ID, F, RD>(
292            &self,
293            iter: I,
294            identity: ID,
295            fold_op: F,
296            reduce_op: RD,
297        ) -> Result<R, E>
298        where
299            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
300            R: Send,
301            E: Send,
302            ID: Fn() -> R + Send + Sync,
303            F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
304            RD: Fn(R, R) -> R + Send + Sync;
305
306        /// Maps each element and collects results into a `Vec`.
307        ///
308        /// This is a convenience method that applies `map_op` to each element and
309        /// collects the results. For [`Sequential`], elements are processed in order.
310        /// For [`Rayon`], elements may be processed out of order but the final
311        /// vector preserves the original ordering.
312        ///
313        /// # Arguments
314        ///
315        /// - `iter`: The collection to map over
316        /// - `map_op`: The mapping function to apply to each element
317        ///
318        /// # Examples
319        ///
320        /// ```
321        /// use commonware_parallel::{Strategy, Sequential};
322        ///
323        /// let strategy = Sequential;
324        /// let data = vec![1, 2, 3, 4, 5];
325        ///
326        /// let squared: Vec<i32> = strategy.map_collect_vec(&data, |&x| x * x);
327        /// assert_eq!(squared, vec![1, 4, 9, 16, 25]);
328        /// ```
329        #[track_caller]
330        fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
331        where
332            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
333            F: Fn(I::Item) -> T + Send + Sync,
334            T: Send,
335        {
336            self.fold(
337                iter,
338                Vec::new,
339                |mut acc, item| {
340                    acc.push(map_op(item));
341                    acc
342                },
343                |mut a, b| {
344                    a.extend(b);
345                    a
346                },
347            )
348        }
349
350        /// Maps each element with a fallible operation and collects results into a `Vec`.
351        ///
352        /// This is a convenience method that applies `map_op` to each element and
353        /// collects the results into a single `Result`. Output ordering on success
354        /// matches [`map_collect_vec`](Self::map_collect_vec). Implementations may stop
355        /// applying `map_op` after an error is observed. When more than one element
356        /// fails, any error may be returned.
357        ///
358        /// # Arguments
359        ///
360        /// - `iter`: The collection to map over
361        /// - `map_op`: The fallible mapping function to apply to each element
362        ///
363        /// # Examples
364        ///
365        /// ```
366        /// use commonware_parallel::{Strategy, Sequential};
367        ///
368        /// let strategy = Sequential;
369        /// let data = vec![1, 2, 3, 4, 5];
370        ///
371        /// let squared: Result<Vec<i32>, ()> = strategy.try_map_collect_vec(
372        ///     &data,
373        ///     |&x| Ok(x * x),
374        /// );
375        /// assert_eq!(squared, Ok(vec![1, 4, 9, 16, 25]));
376        /// ```
377        #[track_caller]
378        fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
379        where
380            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
381            F: Fn(I::Item) -> Result<T, E> + Send + Sync,
382            T: Send,
383            E: Send,
384        {
385            self.try_fold(
386                iter,
387                Vec::new,
388                |mut acc, item| {
389                    acc.push(map_op(item)?);
390                    Ok(acc)
391                },
392                |mut a, b| {
393                    a.extend(b);
394                    a
395                },
396            )
397        }
398
399        /// Maps each element with per-partition state and collects results into a `Vec`.
400        ///
401        /// Combines [`map_collect_vec`](Self::map_collect_vec) with per-partition
402        /// initialization like [`fold_init`](Self::fold_init). Useful when the mapping
403        /// operation requires mutable state that should not be shared across partitions.
404        ///
405        /// # Arguments
406        ///
407        /// - `iter`: The collection to map over
408        /// - `init`: Creates the per-partition initialization value
409        /// - `map_op`: The mapping function: `(&mut init, item) -> result`
410        ///
411        /// # Examples
412        ///
413        /// ```
414        /// use commonware_parallel::{Strategy, Sequential};
415        ///
416        /// let strategy = Sequential;
417        /// let data = vec![1, 2, 3, 4, 5];
418        ///
419        /// // Use a counter that tracks position within each partition
420        /// let indexed: Vec<(usize, i32)> = strategy.map_init_collect_vec(
421        ///     &data,
422        ///     || 0usize, // Per-partition counter
423        ///     |counter, &x| {
424        ///         let idx = *counter;
425        ///         *counter += 1;
426        ///         (idx, x * 2)
427        ///     },
428        /// );
429        ///
430        /// assert_eq!(indexed, vec![(0, 2), (1, 4), (2, 6), (3, 8), (4, 10)]);
431        /// ```
432        #[track_caller]
433        fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
434        where
435            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
436            INIT: Fn() -> T + Send + Sync,
437            T: Send,
438            F: Fn(&mut T, I::Item) -> R + Send + Sync,
439            R: Send,
440        {
441            self.fold_init(
442                iter,
443                init,
444                Vec::new,
445                |mut acc, init_val, item| {
446                    acc.push(map_op(init_val, item));
447                    acc
448                },
449                |mut a, b| {
450                    a.extend(b);
451                    a
452                },
453            )
454        }
455
456        /// Maps each element with per-partition state and a per-item work multiplier.
457        #[track_caller]
458        fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
459            &self,
460            iter: I,
461            _multiplier: usize,
462            init: INIT,
463            map_op: F,
464        ) -> Vec<R>
465        where
466            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
467            INIT: Fn() -> T + Send + Sync,
468            T: Send,
469            F: Fn(&mut T, I::Item) -> R + Send + Sync,
470            R: Send,
471        {
472            self.map_init_collect_vec(iter, init, map_op)
473        }
474
475        /// Maps each element, filtering out `None` results and tracking their keys.
476        ///
477        /// This is a convenience method that applies `map_op` to each element. The
478        /// closure returns `(key, Option<value>)`. Elements where the option is `Some`
479        /// have their values collected into the first vector. Elements where the option
480        /// is `None` have their keys collected into the second vector.
481        ///
482        /// # Arguments
483        ///
484        /// - `iter`: The collection to map over
485        /// - `map_op`: The mapping function returning `(K, Option<U>)`
486        ///
487        /// # Returns
488        ///
489        /// A tuple of `(results, filtered_keys)` where:
490        /// - `results`: Values from successful mappings (where `map_op` returned `Some`)
491        /// - `filtered_keys`: Keys where `map_op` returned `None`
492        ///
493        /// # Examples
494        ///
495        /// ```
496        /// use commonware_parallel::{Strategy, Sequential};
497        ///
498        /// let strategy = Sequential;
499        /// let data = vec![1, 2, 3, 4, 5];
500        ///
501        /// let (evens, odd_values): (Vec<i32>, Vec<i32>) = strategy.map_partition_collect_vec(
502        ///     data.iter(),
503        ///     |&x| (x, if x % 2 == 0 { Some(x * 10) } else { None }),
504        /// );
505        ///
506        /// assert_eq!(evens, vec![20, 40]);
507        /// assert_eq!(odd_values, vec![1, 3, 5]);
508        /// ```
509        #[track_caller]
510        fn map_partition_collect_vec<I, F, K, U>(&self, iter: I, map_op: F) -> (Vec<U>, Vec<K>)
511        where
512            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
513            F: Fn(I::Item) -> (K, Option<U>) + Send + Sync,
514            K: Send,
515            U: Send,
516        {
517            self.fold(
518                iter,
519                || (Vec::new(), Vec::new()),
520                |(mut results, mut filtered), item| {
521                    let (key, value) = map_op(item);
522                    match value {
523                        Some(v) => results.push(v),
524                        None => filtered.push(key),
525                    }
526                    (results, filtered)
527                },
528                |(mut r1, mut f1), (r2, f2)| {
529                    r1.extend(r2);
530                    f1.extend(f2);
531                    (r1, f1)
532                },
533            )
534        }
535
536        /// Executes two closures, potentially in parallel, and returns both results.
537        ///
538        /// For [`Sequential`], this executes `a` then `b` on the current thread.
539        /// For [`Rayon`], this executes `a` and `b` using `rayon::join`.
540        ///
541        /// # Arguments
542        ///
543        /// - `a`: First closure to execute
544        /// - `b`: Second closure to execute
545        ///
546        /// # Examples
547        ///
548        /// ```
549        /// use commonware_parallel::{Strategy, Sequential};
550        ///
551        /// let strategy = Sequential;
552        ///
553        /// let (sum, product) = strategy.join(
554        ///     || (1..=5).sum::<i32>(),
555        ///     || (1..=5).product::<i32>(),
556        /// );
557        ///
558        /// assert_eq!(sum, 15);
559        /// assert_eq!(product, 120);
560        /// ```
561        fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
562        where
563            A: FnOnce() -> RA + Send,
564            B: FnOnce() -> RB + Send,
565            RA: Send,
566            RB: Send;
567
568        /// Sorts a slice with a comparator, preserving the order of equal elements.
569        ///
570        /// # Examples
571        ///
572        /// ```
573        /// use commonware_parallel::{Strategy, Sequential};
574        ///
575        /// let strategy = Sequential;
576        /// let mut data = vec![3, 1, 2];
577        /// strategy.sort_by(&mut data, |a, b| a.cmp(b));
578        /// assert_eq!(data, vec![1, 2, 3]);
579        /// ```
580        #[track_caller]
581        fn sort_by<T, C>(&self, items: &mut [T], compare: C)
582        where
583            T: Send,
584            C: Fn(&T, &T) -> Ordering + Send + Sync;
585    }
586
587    impl<S: Strategy> Strategy for Manual<S> {
588        fn manual(&self) -> Manual<Self> {
589            Manual {
590                strategy: self.clone(),
591                parallelism: self.parallelism,
592            }
593        }
594
595        fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
596        where
597            F: FnOnce(Self) -> T + Send + 'static,
598            T: Send + 'static,
599        {
600            let s = self.clone();
601            self.strategy.spawn(|_| f(s))
602        }
603
604        #[track_caller]
605        fn run<R, SEQ, PAR>(
606            &self,
607            len: usize,
608            serial: SEQ,
609            parallel: PAR,
610        ) -> R
611        where
612            R: Send,
613            SEQ: FnOnce() -> R + Send,
614            PAR: FnOnce() -> R + Send,
615        {
616            self.strategy.run(len, serial, parallel)
617        }
618
619        #[track_caller]
620        fn try_run<R, E, SEQ, PAR>(
621            &self,
622            len: usize,
623            serial: SEQ,
624            parallel: PAR,
625        ) -> Result<R, E>
626        where
627            R: Send,
628            E: Send,
629            SEQ: FnOnce() -> Result<R, E> + Send,
630            PAR: FnOnce() -> Result<R, E> + Send,
631        {
632            self.strategy.try_run(len, serial, parallel)
633        }
634
635        #[track_caller]
636        fn fold_init<I, INIT, T, R, ID, F, RD>(
637            &self,
638            iter: I,
639            init: INIT,
640            identity: ID,
641            fold_op: F,
642            reduce_op: RD,
643        ) -> R
644        where
645            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
646            INIT: Fn() -> T + Send + Sync,
647            T: Send,
648            R: Send,
649            ID: Fn() -> R + Send + Sync,
650            F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
651            RD: Fn(R, R) -> R + Send + Sync,
652        {
653            self.strategy
654                .fold_init(iter, init, identity, fold_op, reduce_op)
655        }
656
657        #[track_caller]
658        fn try_fold<I, R, E, ID, F, RD>(
659            &self,
660            iter: I,
661            identity: ID,
662            fold_op: F,
663            reduce_op: RD,
664        ) -> Result<R, E>
665        where
666            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
667            R: Send,
668            E: Send,
669            ID: Fn() -> R + Send + Sync,
670            F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
671            RD: Fn(R, R) -> R + Send + Sync,
672        {
673            self.strategy.try_fold(iter, identity, fold_op, reduce_op)
674        }
675
676        #[track_caller]
677        fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
678        where
679            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
680            F: Fn(I::Item) -> T + Send + Sync,
681            T: Send,
682        {
683            self.strategy.map_collect_vec(iter, map_op)
684        }
685
686        #[track_caller]
687        fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
688        where
689            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
690            F: Fn(I::Item) -> Result<T, E> + Send + Sync,
691            T: Send,
692            E: Send,
693        {
694            self.strategy.try_map_collect_vec(iter, map_op)
695        }
696
697        #[track_caller]
698        fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
699        where
700            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
701            INIT: Fn() -> T + Send + Sync,
702            T: Send,
703            F: Fn(&mut T, I::Item) -> R + Send + Sync,
704            R: Send,
705        {
706            self.strategy.map_init_collect_vec(iter, init, map_op)
707        }
708
709        #[track_caller]
710        fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
711            &self,
712            iter: I,
713            multiplier: usize,
714            init: INIT,
715            map_op: F,
716        ) -> Vec<R>
717        where
718            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
719            INIT: Fn() -> T + Send + Sync,
720            T: Send,
721            F: Fn(&mut T, I::Item) -> R + Send + Sync,
722            R: Send,
723        {
724            self.strategy
725                .map_init_collect_vec_with_multiplier(iter, multiplier, init, map_op)
726        }
727
728        #[track_caller]
729        fn map_partition_collect_vec<I, F, K, U>(&self, iter: I, map_op: F) -> (Vec<U>, Vec<K>)
730        where
731            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
732            F: Fn(I::Item) -> (K, Option<U>) + Send + Sync,
733            K: Send,
734            U: Send,
735        {
736            self.strategy.map_partition_collect_vec(iter, map_op)
737        }
738
739        fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
740        where
741            A: FnOnce() -> RA + Send,
742            B: FnOnce() -> RB + Send,
743            RA: Send,
744            RB: Send,
745        {
746            self.strategy.join(a, b)
747        }
748
749        #[track_caller]
750        fn sort_by<T, C>(&self, items: &mut [T], compare: C)
751        where
752            T: Send,
753            C: Fn(&T, &T) -> Ordering + Send + Sync,
754        {
755            self.strategy.sort_by(items, compare)
756        }
757    }
758
759    /// A sequential execution strategy.
760    ///
761    /// This strategy executes all operations on the current thread without any
762    /// parallelism. It is useful for:
763    ///
764    /// - Debugging and testing (deterministic execution)
765    /// - `no_std` environments where threading is unavailable
766    /// - Small workloads where parallelism overhead exceeds benefits
767    /// - Comparing sequential vs parallel performance
768    ///
769    /// # Examples
770    ///
771    /// ```
772    /// use commonware_parallel::{Strategy, Sequential};
773    ///
774    /// let strategy = Sequential;
775    /// let data = vec![1, 2, 3, 4, 5];
776    ///
777    /// let sum = strategy.fold(&data, || 0, |a, &b| a + b, |a, b| a + b);
778    /// assert_eq!(sum, 15);
779    /// ```
780    #[derive(Default, Debug, Clone)]
781    pub struct Sequential;
782
783    impl Strategy for Sequential {
784        fn manual(&self) -> Manual<Self> {
785            Manual::new(Self, NonZeroUsize::new(1).unwrap())
786        }
787
788        fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
789        where
790            F: FnOnce(Self) -> T + Send + 'static,
791            T: Send + 'static,
792        {
793            let result = f(self.clone());
794            async move { result }
795        }
796
797        fn run<R, SEQ, PAR>(&self, _len: usize, serial: SEQ, _parallel: PAR) -> R
798        where
799            R: Send,
800            SEQ: FnOnce() -> R + Send,
801            PAR: FnOnce() -> R + Send,
802        {
803            serial()
804        }
805
806        fn try_run<R, E, SEQ, PAR>(&self, _len: usize, serial: SEQ, _parallel: PAR) -> Result<R, E>
807        where
808            R: Send,
809            E: Send,
810            SEQ: FnOnce() -> Result<R, E> + Send,
811            PAR: FnOnce() -> Result<R, E> + Send,
812        {
813            serial()
814        }
815
816        fn fold_init<I, INIT, T, R, ID, F, RD>(
817            &self,
818            iter: I,
819            init: INIT,
820            identity: ID,
821            fold_op: F,
822            _reduce_op: RD,
823        ) -> R
824        where
825            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
826            INIT: Fn() -> T + Send + Sync,
827            T: Send,
828            R: Send,
829            ID: Fn() -> R + Send + Sync,
830            F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
831            RD: Fn(R, R) -> R + Send + Sync,
832        {
833            let mut init_val = init();
834            iter.into_iter()
835                .fold(identity(), |acc, item| fold_op(acc, &mut init_val, item))
836        }
837
838        fn try_fold<I, R, E, ID, F, RD>(
839            &self,
840            iter: I,
841            identity: ID,
842            fold_op: F,
843            _reduce_op: RD,
844        ) -> Result<R, E>
845        where
846            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
847            R: Send,
848            E: Send,
849            ID: Fn() -> R + Send + Sync,
850            F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
851            RD: Fn(R, R) -> R + Send + Sync,
852        {
853            iter.into_iter().try_fold(identity(), fold_op)
854        }
855
856        fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
857        where
858            A: FnOnce() -> RA + Send,
859            B: FnOnce() -> RB + Send,
860            RA: Send,
861            RB: Send,
862        {
863            (a(), b())
864        }
865
866        fn sort_by<T, C>(&self, items: &mut [T], compare: C)
867        where
868            T: Send,
869            C: Fn(&T, &T) -> Ordering + Send + Sync,
870        {
871            items.sort_by(compare);
872        }
873
874    }
875});
876commonware_macros::stability_scope!(BETA, cfg(any(feature = "std", test)) {
877    /// A clone-able wrapper around a [rayon]-compatible thread pool.
878    pub type ThreadPool = Arc<RThreadPool>;
879
880    /// A parallel execution strategy backed by a rayon thread pool.
881    ///
882    /// This strategy adaptively executes collection operations serially or through its backing
883    /// pool. It records wall-clock estimates by callsite, input-size and work-size buckets, and
884    /// planning parallelism so small inputs can avoid rayon scheduling overhead without disabling
885    /// parallel execution for larger inputs.
886    ///
887    /// # Thread Pool Ownership
888    ///
889    /// `Rayon` holds an [`Arc<ThreadPool>`], so it can be cheaply cloned and shared
890    /// across threads. Multiple [`Rayon`] instances can share the same underlying
891    /// thread pool.
892    ///
893    /// # When to Use
894    ///
895    /// Use `Rayon` when:
896    ///
897    /// - Processing large collections where parallelism overhead is justified
898    /// - The fold/reduce operations are CPU-bound
899    /// - You want to utilize multiple cores
900    ///
901    /// Consider [`Sequential`] instead when:
902    ///
903    /// - The collection is small
904    /// - Operations are I/O-bound rather than CPU-bound
905    /// - Deterministic execution order is required for debugging
906    ///
907    /// # Examples
908    ///
909    /// ```rust
910    /// use commonware_parallel::{Strategy, Rayon};
911    /// use std::num::NonZeroUsize;
912    ///
913    /// let strategy = Rayon::new(NonZeroUsize::new(2).unwrap()).unwrap();
914    ///
915    /// let data: Vec<i64> = (0..1000).collect();
916    /// let sum = strategy.fold(&data, || 0i64, |acc, &n| acc + n, |a, b| a + b);
917    /// assert_eq!(sum, 499500);
918    /// ```
919    #[derive(Debug, Clone)]
920    pub struct Rayon {
921        thread_pool: ThreadPool,
922        // The parallelism assumed for policy decisions and manual partitioning. Defaults to the
923        // pool's thread count.
924        parallelism: usize,
925        // `Some` enables adaptive serial-vs-parallel decisions; `None` (used by `manual`) runs the
926        // parallel body whenever the parallelism exceeds one and allocates no policy state.
927        policy: Option<policy::Policy>,
928    }
929
930    impl Rayon {
931        /// Creates a [`Rayon`] strategy with a [`ThreadPool`] that is configured with the given
932        /// number of threads.
933        pub fn new(num_threads: NonZeroUsize) -> Result<Self, ThreadPoolBuildError> {
934            ThreadPoolBuilder::new()
935                .num_threads(num_threads.get())
936                .build()
937                .map(|pool| Self::with_pool(Arc::new(pool)))
938        }
939
940        /// Creates a new [`Rayon`] strategy with the given [`ThreadPool`].
941        pub fn with_pool(thread_pool: ThreadPool) -> Self {
942            let parallelism = thread_pool.current_num_threads().max(1);
943            Self {
944                thread_pool,
945                parallelism,
946                policy: Some(policy::Policy::default()),
947            }
948        }
949
950        /// Overrides the parallelism assumed for planning decisions.
951        ///
952        /// This does not resize the backing pool. By default a strategy plans with the pool's
953        /// thread count; override it when the strategy should expose a different parallelism
954        /// (e.g. a runtime that executes strategy work inline on a single thread).
955        pub const fn with_parallelism(mut self, parallelism: NonZeroUsize) -> Self {
956            self.parallelism = parallelism.get();
957            self
958        }
959
960        #[track_caller]
961        fn execute<R>(
962            &self,
963            len: usize,
964            multiplier: usize,
965            run: impl FnOnce(policy::Execution) -> R,
966        ) -> R {
967            match self.try_execute(len, multiplier, |execution| {
968                Ok::<_, Infallible>(run(execution))
969            }) {
970                Ok(result) => result,
971                Err(e) => match e {},
972            }
973        }
974
975        #[track_caller]
976        fn try_execute<R, E>(
977            &self,
978            len: usize,
979            multiplier: usize,
980            run: impl FnOnce(policy::Execution) -> Result<R, E>,
981        ) -> Result<R, E> {
982            let Some(policy) = &self.policy else {
983                let execution = if self.parallelism <= 1 {
984                    policy::Execution::Serial
985                } else {
986                    policy::Execution::Parallel
987                };
988                return run(execution);
989            };
990
991            let work = len.saturating_mul(multiplier);
992            policy.try_run(Location::caller(), len, work, self.parallelism, run)
993        }
994    }
995
996    impl Strategy for Rayon {
997        fn manual(&self) -> Manual<Self> {
998            Manual {
999                strategy: Self {
1000                    thread_pool: self.thread_pool.clone(),
1001                    parallelism: self.parallelism,
1002                    policy: None,
1003                },
1004                parallelism: self.parallelism,
1005            }
1006        }
1007
1008        fn spawn<F, T>(&self, f: F) -> impl core::future::Future<Output = T> + Send + 'static
1009        where
1010            F: FnOnce(Self) -> T + Send + 'static,
1011            T: Send + 'static,
1012        {
1013            if self.thread_pool.current_num_threads() <= 1 {
1014                return Either::Left(future::ready(f(self.clone())));
1015            }
1016
1017            let (tx, mut rx) = oneshot::channel();
1018            let s = self.clone();
1019            let pool = self.thread_pool.clone();
1020            self.thread_pool.spawn(move || {
1021                // Catch the panic so a panicking job propagates to the awaiting task rather than
1022                // aborting the process (rayon aborts on an uncaught panic in a spawned job).
1023                let result = panic::catch_unwind(AssertUnwindSafe(|| f(s)));
1024                let _ = tx.send(result);
1025            });
1026            Either::Right(async move {
1027                // When the polling thread is itself a member of the pool, waiting on the channel
1028                // could park the only worker able to run the job. Execute pending pool work inline
1029                // until the job completes or another worker takes over. `yield_now` returns `None`
1030                // when this thread is not a pool member, so external callers fall through to the
1031                // channel immediately.
1032                loop {
1033                    if let Ok(Some(result)) = rx.try_recv() {
1034                        return match result {
1035                            Ok(value) => value,
1036                            Err(payload) => panic::resume_unwind(payload),
1037                        };
1038                    }
1039                    if !matches!(pool.yield_now(), Some(Yield::Executed)) {
1040                        break;
1041                    }
1042                }
1043                match rx.await {
1044                    Ok(Ok(value)) => value,
1045                    Ok(Err(payload)) => panic::resume_unwind(payload),
1046                    Err(_) => panic!("strategy job dropped before completion"),
1047                }
1048            })
1049        }
1050
1051        #[track_caller]
1052        fn run<R, SEQ, PAR>(
1053            &self,
1054            len: usize,
1055            serial: SEQ,
1056            parallel: PAR,
1057        ) -> R
1058        where
1059            R: Send,
1060            SEQ: FnOnce() -> R + Send,
1061            PAR: FnOnce() -> R + Send,
1062        {
1063            self.execute(len, 1, |execution| match execution {
1064                policy::Execution::Serial => serial(),
1065                policy::Execution::Parallel => parallel(),
1066            })
1067        }
1068
1069        #[track_caller]
1070        fn try_run<R, E, SEQ, PAR>(
1071            &self,
1072            len: usize,
1073            serial: SEQ,
1074            parallel: PAR,
1075        ) -> Result<R, E>
1076        where
1077            R: Send,
1078            E: Send,
1079            SEQ: FnOnce() -> Result<R, E> + Send,
1080            PAR: FnOnce() -> Result<R, E> + Send,
1081        {
1082            self.try_execute(len, 1, |execution| match execution {
1083                policy::Execution::Serial => serial(),
1084                policy::Execution::Parallel => parallel(),
1085            })
1086        }
1087
1088        #[track_caller]
1089        fn fold_init<I, INIT, T, R, ID, F, RD>(
1090            &self,
1091            iter: I,
1092            init: INIT,
1093            identity: ID,
1094            fold_op: F,
1095            reduce_op: RD,
1096        ) -> R
1097        where
1098            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1099            INIT: Fn() -> T + Send + Sync,
1100            T: Send,
1101            R: Send,
1102            ID: Fn() -> R + Send + Sync,
1103            F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
1104            RD: Fn(R, R) -> R + Send + Sync,
1105        {
1106            let items: Vec<I::Item> = iter.into_iter().collect();
1107            self.execute(items.len(), 1, |execution| match execution {
1108                policy::Execution::Serial => Sequential.fold_init(items, init, identity, fold_op, reduce_op),
1109                policy::Execution::Parallel => self.thread_pool.install(|| {
1110                    items
1111                        .into_par_iter()
1112                        .fold(
1113                            || (init(), identity()),
1114                            |(mut init_val, acc), item| {
1115                                let new_acc = fold_op(acc, &mut init_val, item);
1116                                (init_val, new_acc)
1117                            },
1118                        )
1119                        .map(|(_, acc)| acc)
1120                        .reduce(&identity, reduce_op)
1121                }),
1122            })
1123        }
1124
1125        #[track_caller]
1126        fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
1127        where
1128            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1129            F: Fn(I::Item) -> T + Send + Sync,
1130            T: Send,
1131        {
1132            let items: Vec<I::Item> = iter.into_iter().collect();
1133            self.execute(
1134                items.len(),
1135                1,
1136                |execution| {
1137                    match execution {
1138                        policy::Execution::Serial => Sequential.map_collect_vec(items, map_op),
1139                        policy::Execution::Parallel => self
1140                            .thread_pool
1141                            .install(|| items.into_par_iter().map(map_op).collect()),
1142                    }
1143                },
1144            )
1145        }
1146
1147        #[track_caller]
1148        fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
1149        where
1150            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1151            F: Fn(I::Item) -> Result<T, E> + Send + Sync,
1152            T: Send,
1153            E: Send,
1154        {
1155            let items: Vec<I::Item> = iter.into_iter().collect();
1156            self.try_execute(
1157                items.len(),
1158                1,
1159                |execution| {
1160                    match execution {
1161                        policy::Execution::Serial => Sequential.try_map_collect_vec(items, map_op),
1162                        policy::Execution::Parallel => self
1163                            .thread_pool
1164                            .install(|| items.into_par_iter().map(map_op).collect()),
1165                    }
1166                },
1167            )
1168        }
1169
1170        #[track_caller]
1171        fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
1172        where
1173            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1174            INIT: Fn() -> T + Send + Sync,
1175            T: Send,
1176            F: Fn(&mut T, I::Item) -> R + Send + Sync,
1177            R: Send,
1178        {
1179            let items: Vec<I::Item> = iter.into_iter().collect();
1180            self.execute(
1181                items.len(),
1182                1,
1183                |execution| {
1184                    match execution {
1185                        policy::Execution::Serial => Sequential.map_init_collect_vec(items, init, map_op),
1186                        policy::Execution::Parallel => self
1187                            .thread_pool
1188                            .install(|| items.into_par_iter().map_init(init, map_op).collect()),
1189                    }
1190                },
1191            )
1192        }
1193
1194        #[track_caller]
1195        fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
1196            &self,
1197            iter: I,
1198            multiplier: usize,
1199            init: INIT,
1200            map_op: F,
1201        ) -> Vec<R>
1202        where
1203            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1204            INIT: Fn() -> T + Send + Sync,
1205            T: Send,
1206            F: Fn(&mut T, I::Item) -> R + Send + Sync,
1207            R: Send,
1208        {
1209            let items: Vec<I::Item> = iter.into_iter().collect();
1210            self.execute(
1211                items.len(),
1212                multiplier,
1213                |execution| {
1214                    match execution {
1215                        policy::Execution::Serial => {
1216                            Sequential.map_init_collect_vec(items, init, map_op)
1217                        }
1218                        policy::Execution::Parallel => self
1219                            .thread_pool
1220                            .install(|| items.into_par_iter().map_init(init, map_op).collect()),
1221                    }
1222                },
1223            )
1224        }
1225
1226        #[track_caller]
1227        fn try_fold<I, R, E, ID, F, RD>(
1228            &self,
1229            iter: I,
1230            identity: ID,
1231            fold_op: F,
1232            reduce_op: RD,
1233        ) -> Result<R, E>
1234        where
1235            I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1236            R: Send,
1237            E: Send,
1238            ID: Fn() -> R + Send + Sync,
1239            F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
1240            RD: Fn(R, R) -> R + Send + Sync,
1241        {
1242            let items: Vec<I::Item> = iter.into_iter().collect();
1243            self.try_execute(items.len(), 1, |execution| match execution {
1244                policy::Execution::Serial => Sequential.try_fold(items, identity, fold_op, reduce_op),
1245                policy::Execution::Parallel => self.thread_pool.install(|| {
1246                    items
1247                        .into_par_iter()
1248                        .try_fold(&identity, &fold_op)
1249                        .try_reduce(&identity, |a, b| Ok(reduce_op(a, b)))
1250                }),
1251            })
1252        }
1253
1254        fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
1255        where
1256            A: FnOnce() -> RA + Send,
1257            B: FnOnce() -> RB + Send,
1258            RA: Send,
1259            RB: Send,
1260        {
1261            self.thread_pool.install(|| rayon::join(a, b))
1262        }
1263
1264        #[track_caller]
1265        fn sort_by<T, C>(&self, items: &mut [T], compare: C)
1266        where
1267            T: Send,
1268            C: Fn(&T, &T) -> Ordering + Send + Sync,
1269        {
1270            self.execute(
1271                items.len(),
1272                1,
1273                |execution| match execution {
1274                    policy::Execution::Serial => Sequential.sort_by(items, compare),
1275                    policy::Execution::Parallel => self.thread_pool.install(|| items.par_sort_by(compare)),
1276                },
1277            );
1278        }
1279
1280    }
1281});
1282
1283#[cfg(test)]
1284mod test {
1285    use crate::{Rayon, Sequential, Strategy};
1286    use core::num::NonZeroUsize;
1287    use futures::FutureExt;
1288    use proptest::prelude::*;
1289    use rayon::ThreadPoolBuilder;
1290    use std::sync::{
1291        atomic::{AtomicUsize, Ordering},
1292        Arc,
1293    };
1294
1295    fn parallel_strategy() -> Rayon {
1296        Rayon::new(NonZeroUsize::new(4).unwrap()).unwrap()
1297    }
1298
1299    fn policy_len(strategy: &Rayon) -> usize {
1300        strategy.policy.as_ref().map_or(0, |policy| policy.len())
1301    }
1302
1303    fn map_from_same_callsite(strategy: &Rayon, len: usize) {
1304        let _: Vec<_> = strategy.map_collect_vec(0..len, |x| x);
1305    }
1306
1307    fn map_init_with_multiplier_from_same_callsite(
1308        strategy: &Rayon,
1309        len: usize,
1310        multiplier: usize,
1311    ) {
1312        let _: Vec<_> =
1313            strategy.map_init_collect_vec_with_multiplier(0..len, multiplier, || (), |_, x| x);
1314    }
1315
1316    fn run_from_same_callsite(strategy: &Rayon, len: usize) {
1317        let _: usize = strategy.run(len, || 1, || 2);
1318    }
1319
1320    fn map_partition_from_same_callsite(strategy: &Rayon, len: usize) {
1321        let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..len, |x| {
1322            if x % 2 == 0 {
1323                (x, Some(x))
1324            } else {
1325                (x, None)
1326            }
1327        });
1328    }
1329
1330    #[test]
1331    fn adaptive_policy_is_scoped_to_rayon() {
1332        let strategy = parallel_strategy();
1333        let other = parallel_strategy();
1334
1335        let _: Vec<_> = strategy.map_collect_vec(0..16, |x| x);
1336
1337        assert_eq!(policy_len(&strategy), 1);
1338        assert_eq!(policy_len(&other), 0);
1339    }
1340
1341    /// A spawn awaited from a thread inside the pool must complete even when no other
1342    /// worker can run the job: the pool below registers this thread as a member and never
1343    /// starts its remaining worker, so only the spawn future's yield loop can execute the
1344    /// job (a single poll must suffice; there is no executor to re-poll a pending future).
1345    #[test]
1346    fn spawn_driven_inline_on_member_thread() {
1347        let pool = ThreadPoolBuilder::new()
1348            .num_threads(2)
1349            .use_current_thread()
1350            .spawn_handler(|_| Ok(()))
1351            .build()
1352            .unwrap();
1353        let strategy = Rayon::with_pool(Arc::new(pool));
1354
1355        let result = strategy
1356            .spawn(|strategy| strategy.map_collect_vec(0..2, |i| i + 1))
1357            .now_or_never()
1358            .expect("spawn should complete on first poll via the yield loop");
1359        assert_eq!(result, vec![1, 2]);
1360    }
1361
1362    #[test]
1363    fn with_parallelism_overrides_planning_parallelism() {
1364        let strategy = Rayon::new(NonZeroUsize::new(1).unwrap())
1365            .unwrap()
1366            .with_parallelism(NonZeroUsize::new(4).unwrap());
1367        let strategy = strategy.manual();
1368        assert_eq!(strategy.parallelism(), 4);
1369        assert_eq!(strategy.run(2, || "serial", || "parallel"), "parallel");
1370    }
1371
1372    #[test]
1373    fn adaptive_policy_is_shared_by_clones() {
1374        let strategy = parallel_strategy();
1375        let clone = strategy.clone();
1376
1377        let _: Vec<_> = clone.map_collect_vec(0..16, |x| x);
1378
1379        assert_eq!(policy_len(&strategy), 1);
1380        assert_eq!(policy_len(&clone), 1);
1381    }
1382
1383    #[test]
1384    fn adaptive_policy_records_all_adaptive_operations() {
1385        let strategy = parallel_strategy();
1386
1387        let _: Vec<_> = strategy.fold_init(
1388            0..16,
1389            || (),
1390            Vec::new,
1391            |mut acc, _, x| {
1392                acc.push(x);
1393                acc
1394            },
1395            |mut a, b| {
1396                a.extend(b);
1397                a
1398            },
1399        );
1400        let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
1401        let _: Result<i32, ()> = strategy.try_fold(0..16, || 0, |acc, x| Ok(acc + x), |a, b| a + b);
1402        let _: Vec<_> = strategy.map_collect_vec(0..16, |x| x);
1403        let _: Result<Vec<_>, ()> = strategy.try_map_collect_vec(0..16, Ok);
1404        let _: Vec<_> = strategy.map_init_collect_vec(
1405            0..16,
1406            || AtomicUsize::new(0),
1407            |counter, x| {
1408                counter.fetch_add(1, Ordering::Relaxed);
1409                x
1410            },
1411        );
1412        let _: Vec<_> = strategy.map_init_collect_vec_with_multiplier(
1413            0..16,
1414            2,
1415            || AtomicUsize::new(0),
1416            |counter, x| {
1417                counter.fetch_add(1, Ordering::Relaxed);
1418                x
1419            },
1420        );
1421        let _: usize = strategy.run(16, || 1, || 2);
1422        let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..16, |x| {
1423            if x % 2 == 0 {
1424                (x, Some(x))
1425            } else {
1426                (x, None)
1427            }
1428        });
1429        let _: (i32, i32) = strategy.join(|| 1, || 2);
1430        let mut sortable = vec![3, 2, 1];
1431        strategy.sort_by(&mut sortable, |a, b| a.cmp(b));
1432
1433        assert_eq!(sortable, vec![1, 2, 3]);
1434        assert_eq!(policy_len(&strategy), 10);
1435    }
1436
1437    #[test]
1438    fn adaptive_policy_buckets_by_input_size() {
1439        let strategy = parallel_strategy();
1440
1441        map_from_same_callsite(&strategy, 1);
1442        map_from_same_callsite(&strategy, 2);
1443        map_from_same_callsite(&strategy, 3);
1444
1445        assert_eq!(policy_len(&strategy), 2);
1446    }
1447
1448    #[test]
1449    fn adaptive_policy_buckets_by_work_multiplier() {
1450        let strategy = parallel_strategy();
1451
1452        map_init_with_multiplier_from_same_callsite(&strategy, 16, 1);
1453        map_init_with_multiplier_from_same_callsite(&strategy, 16, 2);
1454        map_init_with_multiplier_from_same_callsite(&strategy, 16, 3);
1455
1456        assert_eq!(policy_len(&strategy), 2);
1457    }
1458
1459    #[test]
1460    fn adaptive_run_buckets_by_input_size() {
1461        let strategy = parallel_strategy();
1462
1463        run_from_same_callsite(&strategy, 1);
1464        run_from_same_callsite(&strategy, 2);
1465        run_from_same_callsite(&strategy, 3);
1466
1467        assert_eq!(policy_len(&strategy), 2);
1468    }
1469
1470    #[test]
1471    fn manual_strategy_does_not_use_adaptive_policy() {
1472        let strategy = parallel_strategy();
1473        let manual = strategy.manual();
1474
1475        let _: usize = manual.fold(0..4, || 0, |acc, x| acc + x, |a, b| a + b);
1476        assert_eq!(manual.run(4, || 1, || 2), 2);
1477
1478        assert_eq!(policy_len(&strategy), 0);
1479        assert_eq!(policy_len(&manual.strategy), 0);
1480    }
1481
1482    #[test]
1483    fn sequential_run_uses_serial_body() {
1484        assert_eq!(Sequential.run(4, || 1, || 2), 1);
1485    }
1486
1487    #[test]
1488    fn adaptive_policy_keys_default_methods_by_external_callsite() {
1489        let strategy = parallel_strategy();
1490
1491        // `fold` uses the trait's default body (no `Rayon` override), so this also guards that
1492        // `#[track_caller]` still attributes the policy key to the caller's line rather than the
1493        // default method body: two calls from distinct callsites must yield two distinct entries.
1494        let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
1495        let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
1496
1497        assert_eq!(policy_len(&strategy), 2);
1498    }
1499
1500    #[test]
1501    fn adaptive_policy_keys_partition_map_by_external_callsite() {
1502        let strategy = parallel_strategy();
1503
1504        map_partition_from_same_callsite(&strategy, 16);
1505        let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..16, |x| {
1506            if x % 2 == 0 {
1507                (x, Some(x))
1508            } else {
1509                (x, None)
1510            }
1511        });
1512
1513        assert_eq!(policy_len(&strategy), 2);
1514    }
1515
1516    #[test]
1517    fn join_does_not_use_adaptive_policy() {
1518        let strategy = parallel_strategy();
1519
1520        let result = strategy.join(|| 1, || 2);
1521
1522        assert_eq!(result, (1, 2));
1523        assert_eq!(policy_len(&strategy), 0);
1524    }
1525
1526    #[test]
1527    fn sequential_spawn_runs_job() {
1528        let result = futures::executor::block_on(Sequential.spawn(|_| 7));
1529
1530        assert_eq!(result, 7);
1531    }
1532
1533    #[test]
1534    fn rayon_spawn_runs_job_on_pool() {
1535        let strategy = parallel_strategy();
1536
1537        let result = futures::executor::block_on(strategy.spawn(|_| {
1538            assert!(rayon::current_thread_index().is_some());
1539            7
1540        }));
1541
1542        assert_eq!(result, 7);
1543        assert_eq!(policy_len(&strategy), 0);
1544    }
1545
1546    #[test]
1547    fn rayon_spawn_runs_inline_on_current_thread_single_worker_pool() {
1548        let pool = ThreadPoolBuilder::new()
1549            .num_threads(1)
1550            .use_current_thread()
1551            .build()
1552            .unwrap();
1553        let strategy =
1554            Rayon::with_pool(Arc::new(pool)).with_parallelism(NonZeroUsize::new(4).unwrap());
1555
1556        assert_eq!(strategy.manual().parallelism(), 4);
1557
1558        let result = strategy.spawn(|_| 7).now_or_never();
1559
1560        assert_eq!(result, Some(7));
1561        assert_eq!(policy_len(&strategy), 0);
1562    }
1563
1564    #[test]
1565    #[should_panic(expected = "boom")]
1566    fn rayon_spawn_propagates_job_panic() {
1567        // A panic on a pool worker must surface at the await point, not abort the process.
1568        let strategy = parallel_strategy();
1569
1570        let _: () = futures::executor::block_on(strategy.spawn(|_| panic!("boom")));
1571    }
1572
1573    #[test]
1574    #[should_panic(expected = "boom")]
1575    fn sequential_spawn_propagates_job_panic() {
1576        let _: () = futures::executor::block_on(Sequential.spawn(|_| panic!("boom")));
1577    }
1578
1579    proptest! {
1580        #[test]
1581        fn parallel_fold_init_matches_sequential(data in prop::collection::vec(any::<i32>(), 0..500)) {
1582            let sequential = Sequential;
1583            let parallel = parallel_strategy();
1584
1585            let seq_result: Vec<i32> = sequential.fold_init(
1586                &data,
1587                || (),
1588                Vec::new,
1589                |mut acc, _, &x| { acc.push(x.wrapping_mul(2)); acc },
1590                |mut a, b| { a.extend(b); a },
1591            );
1592
1593            let par_result: Vec<i32> = parallel.fold_init(
1594                &data,
1595                || (),
1596                Vec::new,
1597                |mut acc, _, &x| { acc.push(x.wrapping_mul(2)); acc },
1598                |mut a, b| { a.extend(b); a },
1599            );
1600
1601            prop_assert_eq!(seq_result, par_result);
1602        }
1603
1604        #[test]
1605        fn fold_equals_fold_init(data in prop::collection::vec(any::<i32>(), 0..500)) {
1606            let s = Sequential;
1607
1608            let via_fold: Vec<i32> = s.fold(
1609                &data,
1610                Vec::new,
1611                |mut acc, &x| { acc.push(x); acc },
1612                |mut a, b| { a.extend(b); a },
1613            );
1614
1615            let via_fold_init: Vec<i32> = s.fold_init(
1616                &data,
1617                || (),
1618                Vec::new,
1619                |mut acc, _, &x| { acc.push(x); acc },
1620                |mut a, b| { a.extend(b); a },
1621            );
1622
1623            prop_assert_eq!(via_fold, via_fold_init);
1624        }
1625
1626        #[test]
1627        fn parallel_try_fold_matches_sequential(data in prop::collection::vec(any::<i32>(), 0..500)) {
1628            let sequential: Result<i32, ()> = Sequential.try_fold(
1629                &data,
1630                || 0i32,
1631                |acc, &x| Ok(acc.wrapping_add(x)),
1632                |a, b| a.wrapping_add(b),
1633            );
1634            let parallel: Result<i32, ()> = parallel_strategy().try_fold(
1635                &data,
1636                || 0i32,
1637                |acc, &x| Ok(acc.wrapping_add(x)),
1638                |a, b| a.wrapping_add(b),
1639            );
1640
1641            prop_assert_eq!(sequential, parallel);
1642        }
1643
1644        #[test]
1645        fn map_collect_vec_equals_fold(data in prop::collection::vec(any::<i32>(), 0..500)) {
1646            let s = Sequential;
1647            let map_op = |&x: &i32| x.wrapping_mul(3);
1648
1649            let via_map: Vec<i32> = s.map_collect_vec(&data, map_op);
1650
1651            let via_fold: Vec<i32> = s.fold(
1652                &data,
1653                Vec::new,
1654                |mut acc, item| { acc.push(map_op(item)); acc },
1655                |mut a, b| { a.extend(b); a },
1656            );
1657
1658            prop_assert_eq!(via_map, via_fold);
1659        }
1660
1661        #[test]
1662        fn try_map_collect_vec_collects_successes(data in prop::collection::vec(any::<i32>(), 0..500)) {
1663            let expected: Vec<i32> = data.iter().map(|x| x.wrapping_mul(5)).collect();
1664
1665            let sequential: Result<Vec<i32>, ()> =
1666                Sequential.try_map_collect_vec(&data, |&x| Ok(x.wrapping_mul(5)));
1667            prop_assert_eq!(sequential, Ok(expected.clone()));
1668
1669            let parallel: Result<Vec<i32>, ()> =
1670                parallel_strategy().try_map_collect_vec(&data, |&x| Ok(x.wrapping_mul(5)));
1671            prop_assert_eq!(parallel, Ok(expected));
1672        }
1673
1674        #[test]
1675        fn try_map_collect_vec_returns_first_error(data in prop::collection::vec(any::<i32>(), 0..500)) {
1676            let expected_error = data.iter().position(|x| x % 7 == 0);
1677            let result: Result<Vec<i32>, usize> =
1678                Sequential.try_map_collect_vec(data.iter().enumerate(), |(i, &x)| {
1679                    if x % 7 == 0 {
1680                        Err(i)
1681                    } else {
1682                        Ok(x)
1683                    }
1684                });
1685
1686            match expected_error {
1687                Some(i) => prop_assert_eq!(result, Err(i)),
1688                None => prop_assert_eq!(result, Ok(data)),
1689            }
1690        }
1691
1692        #[test]
1693        fn map_init_collect_vec_equals_fold_init(data in prop::collection::vec(any::<i32>(), 0..500)) {
1694            let s = Sequential;
1695
1696            let via_map: Vec<i32> = s.map_init_collect_vec(
1697                &data,
1698                || 0i32,
1699                |counter, &x| { *counter += 1; x.wrapping_add(*counter) },
1700            );
1701
1702            let via_fold_init: Vec<i32> = s.fold_init(
1703                &data,
1704                || 0i32,
1705                Vec::new,
1706                |mut acc, counter, &x| {
1707                    *counter += 1;
1708                    acc.push(x.wrapping_add(*counter));
1709                    acc
1710                },
1711                |mut a, b| { a.extend(b); a },
1712            );
1713
1714            prop_assert_eq!(via_map, via_fold_init);
1715        }
1716
1717        #[test]
1718        fn map_partition_collect_vec_returns_valid_results(data in prop::collection::vec(any::<i32>(), 0..500)) {
1719            let s = Sequential;
1720
1721            let map_op = |&x: &i32| {
1722                let value = if x % 2 == 0 { Some(x.wrapping_mul(2)) } else { None };
1723                (x, value)
1724            };
1725
1726            let (results, filtered) = s.map_partition_collect_vec(data.iter(), map_op);
1727
1728            // Verify results contains doubled even numbers
1729            let expected_results: Vec<i32> = data.iter().filter(|&&x| x % 2 == 0).map(|&x| x.wrapping_mul(2)).collect();
1730            prop_assert_eq!(results, expected_results);
1731
1732            // Verify filtered contains odd numbers
1733            let expected_filtered: Vec<i32> = data.iter().filter(|&&x| x % 2 != 0).copied().collect();
1734            prop_assert_eq!(filtered, expected_filtered);
1735        }
1736    }
1737
1738    #[test]
1739    fn try_map_collect_vec_sequential_short_circuits() {
1740        let calls = AtomicUsize::new(0);
1741        let result: Result<Vec<usize>, usize> = Sequential.try_map_collect_vec(0..10, |i| {
1742            calls.fetch_add(1, Ordering::Relaxed);
1743            if i == 3 {
1744                Err(i)
1745            } else {
1746                Ok(i)
1747            }
1748        });
1749
1750        assert_eq!(result, Err(3));
1751        assert_eq!(calls.load(Ordering::Relaxed), 4);
1752    }
1753
1754    #[test]
1755    fn try_map_collect_vec_parallel_returns_an_error() {
1756        let result: Result<Vec<usize>, usize> =
1757            parallel_strategy().try_map_collect_vec(0..128, |i| {
1758                if i == 17 || i == 42 {
1759                    Err(i)
1760                } else {
1761                    Ok(i)
1762                }
1763            });
1764
1765        assert!(matches!(result, Err(17 | 42)));
1766    }
1767}