1#![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};
72
73 cfg_if! {
74 if #[cfg(any(feature = "std", test))] {
75 use core::{convert::Infallible, num::NonZeroUsize};
76 use futures::{
77 channel::oneshot,
78 future::{self, Either},
79 };
80 use rayon::{
81 ThreadPool as RThreadPool, ThreadPoolBuildError, ThreadPoolBuilder, Yield,
82 iter::{IntoParallelIterator, ParallelIterator},
83 slice::ParallelSliceMut,
84 };
85 use std::{
86 panic::{self, AssertUnwindSafe, Location},
87 sync::Arc,
88 time::Instant,
89 };
90
91 mod policy;
92 } else {
93 extern crate alloc;
94 use alloc::vec::Vec;
95 }
96 }
97
98 #[derive(Clone, Debug)]
104 pub struct Manual<S> {
105 strategy: S,
106 parallelism: usize,
107 }
108
109 impl<S> Manual<S> {
110 pub const fn parallelism(&self) -> usize {
112 self.parallelism
113 }
114 }
115
116 pub trait Strategy: Clone + Send + Sync + fmt::Debug + 'static {
122 fn manual(&self) -> Manual<Self>
124 where
125 Self: Sized;
126
127 #[track_caller]
143 fn spawn<F, T>(
144 &self,
145 len: usize,
146 f: F,
147 ) -> impl core::future::Future<Output = T> + Send + 'static
148 where
149 F: FnOnce(Self) -> T + Send + 'static,
150 T: Send + 'static;
151
152 #[track_caller]
154 fn run<R, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> R
155 where
156 R: Send,
157 SEQ: FnOnce() -> R + Send,
158 PAR: FnOnce() -> R + Send;
159
160 #[track_caller]
166 fn try_run<R, E, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> Result<R, E>
167 where
168 R: Send,
169 E: Send,
170 SEQ: FnOnce() -> Result<R, E> + Send,
171 PAR: FnOnce() -> Result<R, E> + Send;
172
173 #[track_caller]
214 fn fold_init<I, INIT, T, R, ID, F, RD>(
215 &self,
216 iter: I,
217 init: INIT,
218 identity: ID,
219 fold_op: F,
220 reduce_op: RD,
221 ) -> R
222 where
223 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
224 INIT: Fn() -> T + Send + Sync,
225 T: Send,
226 R: Send,
227 ID: Fn() -> R + Send + Sync,
228 F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
229 RD: Fn(R, R) -> R + Send + Sync;
230
231 #[track_caller]
263 fn fold<I, R, ID, F, RD>(&self, iter: I, identity: ID, fold_op: F, reduce_op: RD) -> R
264 where
265 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
266 R: Send,
267 ID: Fn() -> R + Send + Sync,
268 F: Fn(R, I::Item) -> R + Send + Sync,
269 RD: Fn(R, R) -> R + Send + Sync,
270 {
271 self.fold_init(
272 iter,
273 || (),
274 identity,
275 |acc, _, item| fold_op(acc, item),
276 reduce_op,
277 )
278 }
279
280 #[track_caller]
296 fn try_fold<I, R, E, ID, F, RD>(
297 &self,
298 iter: I,
299 identity: ID,
300 fold_op: F,
301 reduce_op: RD,
302 ) -> Result<R, E>
303 where
304 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
305 R: Send,
306 E: Send,
307 ID: Fn() -> R + Send + Sync,
308 F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
309 RD: Fn(R, R) -> R + Send + Sync;
310
311 #[track_caller]
335 fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
336 where
337 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
338 F: Fn(I::Item) -> T + Send + Sync,
339 T: Send,
340 {
341 self.fold(
342 iter,
343 Vec::new,
344 |mut acc, item| {
345 acc.push(map_op(item));
346 acc
347 },
348 |mut a, b| {
349 a.extend(b);
350 a
351 },
352 )
353 }
354
355 #[track_caller]
383 fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
384 where
385 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
386 F: Fn(I::Item) -> Result<T, E> + Send + Sync,
387 T: Send,
388 E: Send,
389 {
390 self.try_fold(
391 iter,
392 Vec::new,
393 |mut acc, item| {
394 acc.push(map_op(item)?);
395 Ok(acc)
396 },
397 |mut a, b| {
398 a.extend(b);
399 a
400 },
401 )
402 }
403
404 #[track_caller]
438 fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
439 where
440 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
441 INIT: Fn() -> T + Send + Sync,
442 T: Send,
443 F: Fn(&mut T, I::Item) -> R + Send + Sync,
444 R: Send,
445 {
446 self.fold_init(
447 iter,
448 init,
449 Vec::new,
450 |mut acc, init_val, item| {
451 acc.push(map_op(init_val, item));
452 acc
453 },
454 |mut a, b| {
455 a.extend(b);
456 a
457 },
458 )
459 }
460
461 #[track_caller]
463 fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
464 &self,
465 iter: I,
466 _multiplier: usize,
467 init: INIT,
468 map_op: F,
469 ) -> Vec<R>
470 where
471 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
472 INIT: Fn() -> T + Send + Sync,
473 T: Send,
474 F: Fn(&mut T, I::Item) -> R + Send + Sync,
475 R: Send,
476 {
477 self.map_init_collect_vec(iter, init, map_op)
478 }
479
480 #[track_caller]
486 fn map_collect_vec_with_multiplier<I, F, R>(
487 &self,
488 iter: I,
489 multiplier: usize,
490 map_op: F,
491 ) -> Vec<R>
492 where
493 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
494 F: Fn(I::Item) -> R + Send + Sync,
495 R: Send,
496 {
497 self.map_init_collect_vec_with_multiplier(iter, multiplier, || (), |_, item| {
498 map_op(item)
499 })
500 }
501
502 #[track_caller]
537 fn map_partition_collect_vec<I, F, K, U>(&self, iter: I, map_op: F) -> (Vec<U>, Vec<K>)
538 where
539 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
540 F: Fn(I::Item) -> (K, Option<U>) + Send + Sync,
541 K: Send,
542 U: Send,
543 {
544 self.fold(
545 iter,
546 || (Vec::new(), Vec::new()),
547 |(mut results, mut filtered), item| {
548 let (key, value) = map_op(item);
549 match value {
550 Some(v) => results.push(v),
551 None => filtered.push(key),
552 }
553 (results, filtered)
554 },
555 |(mut r1, mut f1), (r2, f2)| {
556 r1.extend(r2);
557 f1.extend(f2);
558 (r1, f1)
559 },
560 )
561 }
562
563 fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
589 where
590 A: FnOnce() -> RA + Send,
591 B: FnOnce() -> RB + Send,
592 RA: Send,
593 RB: Send;
594
595 #[track_caller]
608 fn sort_by<T, C>(&self, items: &mut [T], compare: C)
609 where
610 T: Send,
611 C: Fn(&T, &T) -> Ordering + Send + Sync;
612 }
613
614 impl<S: Strategy> Strategy for Manual<S> {
615 fn manual(&self) -> Manual<Self> {
616 Manual {
617 strategy: self.clone(),
618 parallelism: self.parallelism,
619 }
620 }
621
622 #[track_caller]
623 fn spawn<F, T>(
624 &self,
625 len: usize,
626 f: F,
627 ) -> impl core::future::Future<Output = T> + Send + 'static
628 where
629 F: FnOnce(Self) -> T + Send + 'static,
630 T: Send + 'static,
631 {
632 let s = self.clone();
633 self.strategy.spawn(len, |_| f(s))
634 }
635
636 #[track_caller]
637 fn run<R, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> R
638 where
639 R: Send,
640 SEQ: FnOnce() -> R + Send,
641 PAR: FnOnce() -> R + Send,
642 {
643 self.strategy.run(len, serial, parallel)
644 }
645
646 #[track_caller]
647 fn try_run<R, E, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> Result<R, E>
648 where
649 R: Send,
650 E: Send,
651 SEQ: FnOnce() -> Result<R, E> + Send,
652 PAR: FnOnce() -> Result<R, E> + Send,
653 {
654 self.strategy.try_run(len, serial, parallel)
655 }
656
657 #[track_caller]
658 fn fold_init<I, INIT, T, R, ID, F, RD>(
659 &self,
660 iter: I,
661 init: INIT,
662 identity: ID,
663 fold_op: F,
664 reduce_op: RD,
665 ) -> R
666 where
667 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
668 INIT: Fn() -> T + Send + Sync,
669 T: Send,
670 R: Send,
671 ID: Fn() -> R + Send + Sync,
672 F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
673 RD: Fn(R, R) -> R + Send + Sync,
674 {
675 self.strategy
676 .fold_init(iter, init, identity, fold_op, reduce_op)
677 }
678
679 #[track_caller]
680 fn try_fold<I, R, E, ID, F, RD>(
681 &self,
682 iter: I,
683 identity: ID,
684 fold_op: F,
685 reduce_op: RD,
686 ) -> Result<R, E>
687 where
688 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
689 R: Send,
690 E: Send,
691 ID: Fn() -> R + Send + Sync,
692 F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
693 RD: Fn(R, R) -> R + Send + Sync,
694 {
695 self.strategy.try_fold(iter, identity, fold_op, reduce_op)
696 }
697
698 #[track_caller]
699 fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
700 where
701 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
702 F: Fn(I::Item) -> T + Send + Sync,
703 T: Send,
704 {
705 self.strategy.map_collect_vec(iter, map_op)
706 }
707
708 #[track_caller]
709 fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
710 where
711 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
712 F: Fn(I::Item) -> Result<T, E> + Send + Sync,
713 T: Send,
714 E: Send,
715 {
716 self.strategy.try_map_collect_vec(iter, map_op)
717 }
718
719 #[track_caller]
720 fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
721 where
722 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
723 INIT: Fn() -> T + Send + Sync,
724 T: Send,
725 F: Fn(&mut T, I::Item) -> R + Send + Sync,
726 R: Send,
727 {
728 self.strategy.map_init_collect_vec(iter, init, map_op)
729 }
730
731 #[track_caller]
732 fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
733 &self,
734 iter: I,
735 multiplier: usize,
736 init: INIT,
737 map_op: F,
738 ) -> Vec<R>
739 where
740 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
741 INIT: Fn() -> T + Send + Sync,
742 T: Send,
743 F: Fn(&mut T, I::Item) -> R + Send + Sync,
744 R: Send,
745 {
746 self.strategy
747 .map_init_collect_vec_with_multiplier(iter, multiplier, init, map_op)
748 }
749
750 #[track_caller]
751 fn map_partition_collect_vec<I, F, K, U>(&self, iter: I, map_op: F) -> (Vec<U>, Vec<K>)
752 where
753 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
754 F: Fn(I::Item) -> (K, Option<U>) + Send + Sync,
755 K: Send,
756 U: Send,
757 {
758 self.strategy.map_partition_collect_vec(iter, map_op)
759 }
760
761 fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
762 where
763 A: FnOnce() -> RA + Send,
764 B: FnOnce() -> RB + Send,
765 RA: Send,
766 RB: Send,
767 {
768 self.strategy.join(a, b)
769 }
770
771 #[track_caller]
772 fn sort_by<T, C>(&self, items: &mut [T], compare: C)
773 where
774 T: Send,
775 C: Fn(&T, &T) -> Ordering + Send + Sync,
776 {
777 self.strategy.sort_by(items, compare)
778 }
779 }
780
781 #[derive(Default, Debug, Clone)]
803 pub struct Sequential;
804
805 impl Strategy for Sequential {
806 fn manual(&self) -> Manual<Self> {
807 Manual {
808 strategy: Self,
809 parallelism: 1,
810 }
811 }
812
813 fn spawn<F, T>(
814 &self,
815 _len: usize,
816 f: F,
817 ) -> impl core::future::Future<Output = T> + Send + 'static
818 where
819 F: FnOnce(Self) -> T + Send + 'static,
820 T: Send + 'static,
821 {
822 let result = f(self.clone());
823 async move { result }
824 }
825
826 fn run<R, SEQ, PAR>(&self, _len: usize, serial: SEQ, _parallel: PAR) -> R
827 where
828 R: Send,
829 SEQ: FnOnce() -> R + Send,
830 PAR: FnOnce() -> R + Send,
831 {
832 serial()
833 }
834
835 fn try_run<R, E, SEQ, PAR>(&self, _len: usize, serial: SEQ, _parallel: PAR) -> Result<R, E>
836 where
837 R: Send,
838 E: Send,
839 SEQ: FnOnce() -> Result<R, E> + Send,
840 PAR: FnOnce() -> Result<R, E> + Send,
841 {
842 serial()
843 }
844
845 fn fold_init<I, INIT, T, R, ID, F, RD>(
846 &self,
847 iter: I,
848 init: INIT,
849 identity: ID,
850 fold_op: F,
851 _reduce_op: RD,
852 ) -> R
853 where
854 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
855 INIT: Fn() -> T + Send + Sync,
856 T: Send,
857 R: Send,
858 ID: Fn() -> R + Send + Sync,
859 F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
860 RD: Fn(R, R) -> R + Send + Sync,
861 {
862 let mut init_val = init();
863 iter.into_iter()
864 .fold(identity(), |acc, item| fold_op(acc, &mut init_val, item))
865 }
866
867 fn try_fold<I, R, E, ID, F, RD>(
868 &self,
869 iter: I,
870 identity: ID,
871 fold_op: F,
872 _reduce_op: RD,
873 ) -> Result<R, E>
874 where
875 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
876 R: Send,
877 E: Send,
878 ID: Fn() -> R + Send + Sync,
879 F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
880 RD: Fn(R, R) -> R + Send + Sync,
881 {
882 iter.into_iter().try_fold(identity(), fold_op)
883 }
884
885 fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
886 where
887 A: FnOnce() -> RA + Send,
888 B: FnOnce() -> RB + Send,
889 RA: Send,
890 RB: Send,
891 {
892 (a(), b())
893 }
894
895 fn sort_by<T, C>(&self, items: &mut [T], compare: C)
896 where
897 T: Send,
898 C: Fn(&T, &T) -> Ordering + Send + Sync,
899 {
900 items.sort_by(compare);
901 }
902 }
903});
904commonware_macros::stability_scope!(BETA, cfg(any(feature = "std", test)) {
905 pub type ThreadPool = Arc<RThreadPool>;
907
908 #[derive(Debug, Clone)]
948 pub struct Rayon {
949 thread_pool: ThreadPool,
950 parallelism: usize,
953 policy: Option<policy::Policy>,
956 }
957
958 impl Rayon {
959 pub fn new(num_threads: NonZeroUsize) -> Result<Self, ThreadPoolBuildError> {
962 ThreadPoolBuilder::new()
963 .num_threads(num_threads.get())
964 .build()
965 .map(|pool| Self::with_pool(Arc::new(pool)))
966 }
967
968 pub fn with_pool(thread_pool: ThreadPool) -> Self {
970 let parallelism = thread_pool.current_num_threads().max(1);
971 Self {
972 thread_pool,
973 parallelism,
974 policy: Some(policy::Policy::default()),
975 }
976 }
977
978 pub const fn with_parallelism(mut self, parallelism: NonZeroUsize) -> Self {
984 self.parallelism = parallelism.get();
985 self
986 }
987
988 #[track_caller]
989 fn execute<R>(
990 &self,
991 len: usize,
992 multiplier: usize,
993 run: impl FnOnce(policy::RunExecution) -> R,
994 ) -> R {
995 match self.try_execute(len, multiplier, |execution| {
996 Ok::<_, Infallible>(run(execution))
997 }) {
998 Ok(result) => result,
999 Err(e) => match e {},
1000 }
1001 }
1002
1003 #[track_caller]
1004 fn try_execute<R, E>(
1005 &self,
1006 len: usize,
1007 multiplier: usize,
1008 run: impl FnOnce(policy::RunExecution) -> Result<R, E>,
1009 ) -> Result<R, E> {
1010 let Some(policy) = &self.policy else {
1011 let execution = if self.parallelism <= 1 {
1012 policy::RunExecution::Serial
1013 } else {
1014 policy::RunExecution::Parallel
1015 };
1016 return run(execution);
1017 };
1018
1019 let work = len.saturating_mul(multiplier);
1020 policy.try_run(Location::caller(), len, work, self.parallelism, run)
1021 }
1022 }
1023
1024 impl Strategy for Rayon {
1025 fn manual(&self) -> Manual<Self> {
1026 Manual {
1027 strategy: Self {
1028 thread_pool: self.thread_pool.clone(),
1029 parallelism: self.parallelism,
1030 policy: None,
1031 },
1032 parallelism: self.parallelism,
1033 }
1034 }
1035
1036 #[track_caller]
1037 fn spawn<F, T>(
1038 &self,
1039 len: usize,
1040 f: F,
1041 ) -> impl core::future::Future<Output = T> + Send + 'static
1042 where
1043 F: FnOnce(Self) -> T + Send + 'static,
1044 T: Send + 'static,
1045 {
1046 let threads = self.thread_pool.current_num_threads();
1047 let caller = Location::caller();
1048
1049 let ((execution, measure), policy) = if threads <= 1 {
1054 ((policy::SpawnExecution::Inline, false), None)
1055 } else {
1056 self.policy.as_ref().map_or(
1057 ((policy::SpawnExecution::Offload, false), None),
1058 |policy| (policy.choose_spawn(caller, len, threads), Some(policy)),
1059 )
1060 };
1061
1062 match execution {
1063 policy::SpawnExecution::Inline => {
1064 let start = measure.then(Instant::now);
1066 let result = f(self.clone());
1067 if let (Some(start), Some(policy)) = (start, policy) {
1068 policy.record_spawn_inline(caller, len, threads, start.elapsed());
1069 }
1070 Either::Left(future::ready(result))
1071 }
1072 policy::SpawnExecution::Offload => {
1073 let spawn_start = measure.then(Instant::now);
1077 let (tx, mut rx) = oneshot::channel();
1078 let s = self.clone();
1079 let pool = self.thread_pool.clone();
1080 let recorder = if measure {
1081 policy.cloned().map(|policy| (policy, caller, len, threads))
1082 } else {
1083 None
1084 };
1085 let worker_recorder = recorder.clone();
1086 self.thread_pool.spawn(move || {
1087 let job_start = worker_recorder.is_some().then(Instant::now);
1088
1089 let result = panic::catch_unwind(AssertUnwindSafe(|| f(s)));
1093 let job = job_start.map(|start| start.elapsed());
1094 let ok = result.is_ok();
1095 let _ = tx.send((result, job));
1096
1097 if ok
1101 && let (Some((policy, caller, len, threads)), Some(job)) =
1102 (worker_recorder, job)
1103 {
1104 policy.record_spawn_job(caller, len, threads, job);
1105 }
1106 });
1107 Either::Right(async move {
1108 let (result, job) = loop {
1114 if let Ok(Some(payload)) = rx.try_recv() {
1115 break payload;
1116 }
1117 if !matches!(pool.yield_now(), Some(Yield::Executed)) {
1118 break rx.await.unwrap_or_else(|_| {
1119 panic!("strategy job dropped before completion")
1120 });
1121 }
1122 };
1123 match result {
1124 Ok(value) => {
1125 if let (
1131 Some((policy, caller, len, threads)),
1132 Some(job),
1133 Some(start),
1134 ) = (recorder, job, spawn_start)
1135 {
1136 policy.record_spawn_overhead(
1137 caller,
1138 len,
1139 threads,
1140 start.elapsed().saturating_sub(job),
1141 );
1142 }
1143 value
1144 }
1145 Err(payload) => panic::resume_unwind(payload),
1146 }
1147 })
1148 }
1149 }
1150 }
1151
1152 #[track_caller]
1153 fn run<R, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> R
1154 where
1155 R: Send,
1156 SEQ: FnOnce() -> R + Send,
1157 PAR: FnOnce() -> R + Send,
1158 {
1159 self.execute(len, 1, |execution| match execution {
1160 policy::RunExecution::Serial => serial(),
1161 policy::RunExecution::Parallel => parallel(),
1162 })
1163 }
1164
1165 #[track_caller]
1166 fn try_run<R, E, SEQ, PAR>(&self, len: usize, serial: SEQ, parallel: PAR) -> Result<R, E>
1167 where
1168 R: Send,
1169 E: Send,
1170 SEQ: FnOnce() -> Result<R, E> + Send,
1171 PAR: FnOnce() -> Result<R, E> + Send,
1172 {
1173 self.try_execute(len, 1, |execution| match execution {
1174 policy::RunExecution::Serial => serial(),
1175 policy::RunExecution::Parallel => parallel(),
1176 })
1177 }
1178
1179 #[track_caller]
1180 fn fold_init<I, INIT, T, R, ID, F, RD>(
1181 &self,
1182 iter: I,
1183 init: INIT,
1184 identity: ID,
1185 fold_op: F,
1186 reduce_op: RD,
1187 ) -> R
1188 where
1189 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1190 INIT: Fn() -> T + Send + Sync,
1191 T: Send,
1192 R: Send,
1193 ID: Fn() -> R + Send + Sync,
1194 F: Fn(R, &mut T, I::Item) -> R + Send + Sync,
1195 RD: Fn(R, R) -> R + Send + Sync,
1196 {
1197 let items: Vec<I::Item> = iter.into_iter().collect();
1198 self.execute(items.len(), 1, |execution| match execution {
1199 policy::RunExecution::Serial => {
1200 Sequential.fold_init(items, init, identity, fold_op, reduce_op)
1201 }
1202 policy::RunExecution::Parallel => self.thread_pool.install(|| {
1203 items
1204 .into_par_iter()
1205 .fold(
1206 || (init(), identity()),
1207 |(mut init_val, acc), item| {
1208 let new_acc = fold_op(acc, &mut init_val, item);
1209 (init_val, new_acc)
1210 },
1211 )
1212 .map(|(_, acc)| acc)
1213 .reduce(&identity, reduce_op)
1214 }),
1215 })
1216 }
1217
1218 #[track_caller]
1219 fn map_collect_vec<I, F, T>(&self, iter: I, map_op: F) -> Vec<T>
1220 where
1221 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1222 F: Fn(I::Item) -> T + Send + Sync,
1223 T: Send,
1224 {
1225 let items: Vec<I::Item> = iter.into_iter().collect();
1226 self.execute(items.len(), 1, |execution| match execution {
1227 policy::RunExecution::Serial => Sequential.map_collect_vec(items, map_op),
1228 policy::RunExecution::Parallel => self
1229 .thread_pool
1230 .install(|| items.into_par_iter().map(map_op).collect()),
1231 })
1232 }
1233
1234 #[track_caller]
1235 fn try_map_collect_vec<I, F, T, E>(&self, iter: I, map_op: F) -> Result<Vec<T>, E>
1236 where
1237 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1238 F: Fn(I::Item) -> Result<T, E> + Send + Sync,
1239 T: Send,
1240 E: Send,
1241 {
1242 let items: Vec<I::Item> = iter.into_iter().collect();
1243 self.try_execute(items.len(), 1, |execution| match execution {
1244 policy::RunExecution::Serial => Sequential.try_map_collect_vec(items, map_op),
1245 policy::RunExecution::Parallel => self
1246 .thread_pool
1247 .install(|| items.into_par_iter().map(map_op).collect()),
1248 })
1249 }
1250
1251 #[track_caller]
1252 fn map_init_collect_vec<I, INIT, T, F, R>(&self, iter: I, init: INIT, map_op: F) -> Vec<R>
1253 where
1254 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1255 INIT: Fn() -> T + Send + Sync,
1256 T: Send,
1257 F: Fn(&mut T, I::Item) -> R + Send + Sync,
1258 R: Send,
1259 {
1260 let items: Vec<I::Item> = iter.into_iter().collect();
1261 self.execute(items.len(), 1, |execution| match execution {
1262 policy::RunExecution::Serial => Sequential.map_init_collect_vec(items, init, map_op),
1263 policy::RunExecution::Parallel => self
1264 .thread_pool
1265 .install(|| items.into_par_iter().map_init(init, map_op).collect()),
1266 })
1267 }
1268
1269 #[track_caller]
1270 fn map_init_collect_vec_with_multiplier<I, INIT, T, F, R>(
1271 &self,
1272 iter: I,
1273 multiplier: usize,
1274 init: INIT,
1275 map_op: F,
1276 ) -> Vec<R>
1277 where
1278 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1279 INIT: Fn() -> T + Send + Sync,
1280 T: Send,
1281 F: Fn(&mut T, I::Item) -> R + Send + Sync,
1282 R: Send,
1283 {
1284 let items: Vec<I::Item> = iter.into_iter().collect();
1285 self.execute(items.len(), multiplier, |execution| match execution {
1286 policy::RunExecution::Serial => Sequential.map_init_collect_vec(items, init, map_op),
1287 policy::RunExecution::Parallel => self
1288 .thread_pool
1289 .install(|| items.into_par_iter().map_init(init, map_op).collect()),
1290 })
1291 }
1292
1293 #[track_caller]
1294 fn try_fold<I, R, E, ID, F, RD>(
1295 &self,
1296 iter: I,
1297 identity: ID,
1298 fold_op: F,
1299 reduce_op: RD,
1300 ) -> Result<R, E>
1301 where
1302 I: IntoIterator<IntoIter: Send, Item: Send> + Send,
1303 R: Send,
1304 E: Send,
1305 ID: Fn() -> R + Send + Sync,
1306 F: Fn(R, I::Item) -> Result<R, E> + Send + Sync,
1307 RD: Fn(R, R) -> R + Send + Sync,
1308 {
1309 let items: Vec<I::Item> = iter.into_iter().collect();
1310 self.try_execute(items.len(), 1, |execution| match execution {
1311 policy::RunExecution::Serial => {
1312 Sequential.try_fold(items, identity, fold_op, reduce_op)
1313 }
1314 policy::RunExecution::Parallel => self.thread_pool.install(|| {
1315 items
1316 .into_par_iter()
1317 .try_fold(&identity, &fold_op)
1318 .try_reduce(&identity, |a, b| Ok(reduce_op(a, b)))
1319 }),
1320 })
1321 }
1322
1323 fn join<A, B, RA, RB>(&self, a: A, b: B) -> (RA, RB)
1324 where
1325 A: FnOnce() -> RA + Send,
1326 B: FnOnce() -> RB + Send,
1327 RA: Send,
1328 RB: Send,
1329 {
1330 self.thread_pool.install(|| rayon::join(a, b))
1331 }
1332
1333 #[track_caller]
1334 fn sort_by<T, C>(&self, items: &mut [T], compare: C)
1335 where
1336 T: Send,
1337 C: Fn(&T, &T) -> Ordering + Send + Sync,
1338 {
1339 self.execute(items.len(), 1, |execution| match execution {
1340 policy::RunExecution::Serial => Sequential.sort_by(items, compare),
1341 policy::RunExecution::Parallel => {
1342 self.thread_pool.install(|| items.par_sort_by(compare))
1343 }
1344 });
1345 }
1346 }
1347});
1348commonware_macros::stability_scope!(ALPHA, cfg(any(feature = "test-utils", test)) {
1349 pub mod mocks;
1350});
1351
1352#[cfg(test)]
1353mod test {
1354 use crate::{Rayon, Sequential, Strategy};
1355 use core::num::NonZeroUsize;
1356 use futures::FutureExt;
1357 use proptest::prelude::*;
1358 use rayon::ThreadPoolBuilder;
1359 use std::sync::{
1360 Arc,
1361 atomic::{AtomicUsize, Ordering},
1362 };
1363
1364 fn parallel_strategy() -> Rayon {
1365 Rayon::new(NonZeroUsize::new(4).unwrap()).unwrap()
1366 }
1367
1368 #[track_caller]
1371 fn spawn_flagged(
1372 strategy: &Rayon,
1373 panics: bool,
1374 ) -> (
1375 &'static std::panic::Location<'static>,
1376 impl core::future::Future<Output = usize> + Send + 'static,
1377 ) {
1378 (
1379 std::panic::Location::caller(),
1380 strategy.spawn(64, move |_| {
1381 if panics {
1382 panic!("job panic");
1383 }
1384 7
1385 }),
1386 )
1387 }
1388
1389 fn spawn_recorded(strategy: &Rayon, loc: &'static std::panic::Location<'static>) -> bool {
1390 let parallelism = strategy.manual().parallelism();
1391 strategy
1392 .policy
1393 .as_ref()
1394 .is_some_and(|policy| policy.spawn_recorded(loc, 64, parallelism))
1395 }
1396
1397 #[test]
1400 fn spawn_panic_records_nothing() {
1401 let strategy = parallel_strategy();
1402
1403 let (loc, job) = spawn_flagged(&strategy, true);
1404 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1405 futures::executor::block_on(job)
1406 }));
1407 assert!(result.is_err());
1408 assert!(!spawn_recorded(&strategy, loc));
1409
1410 let (loc, job) = spawn_flagged(&strategy, false);
1411 assert_eq!(futures::executor::block_on(job), 7);
1412 assert!(spawn_recorded(&strategy, loc));
1413 }
1414
1415 #[test]
1418 fn spawn_converges_inline_for_tiny_jobs() {
1419 let strategy = parallel_strategy();
1420
1421 for _ in 0..100 {
1422 let on_pool = futures::executor::block_on(
1423 strategy.spawn(64, |_| rayon::current_thread_index().is_some()),
1424 );
1425 if !on_pool {
1426 return;
1427 }
1428 }
1429 panic!("a trivial job never converged to inline placement");
1430 }
1431
1432 #[test]
1435 fn spawn_keeps_offloading_big_jobs() {
1436 let strategy = parallel_strategy();
1437
1438 for _ in 0..20 {
1439 let on_pool = futures::executor::block_on(strategy.spawn(64, |_| {
1440 std::thread::sleep(std::time::Duration::from_millis(2));
1441 rayon::current_thread_index().is_some()
1442 }));
1443 assert!(
1444 on_pool,
1445 "a job over the inline budget ran on the calling task"
1446 );
1447 }
1448 }
1449
1450 fn policy_len(strategy: &Rayon) -> usize {
1451 strategy.policy.as_ref().map_or(0, |policy| policy.len())
1452 }
1453
1454 fn map_from_same_callsite(strategy: &Rayon, len: usize) {
1455 let _: Vec<_> = strategy.map_collect_vec(0..len, |x| x);
1456 }
1457
1458 fn map_init_with_multiplier_from_same_callsite(
1459 strategy: &Rayon,
1460 len: usize,
1461 multiplier: usize,
1462 ) {
1463 let _: Vec<_> =
1464 strategy.map_init_collect_vec_with_multiplier(0..len, multiplier, || (), |_, x| x);
1465 }
1466
1467 fn run_from_same_callsite(strategy: &Rayon, len: usize) {
1468 let _: usize = strategy.run(len, || 1, || 2);
1469 }
1470
1471 fn map_partition_from_same_callsite(strategy: &Rayon, len: usize) {
1472 let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..len, |x| {
1473 if x % 2 == 0 { (x, Some(x)) } else { (x, None) }
1474 });
1475 }
1476
1477 #[test]
1478 fn adaptive_policy_is_scoped_to_rayon() {
1479 let strategy = parallel_strategy();
1480 let other = parallel_strategy();
1481
1482 let _: Vec<_> = strategy.map_collect_vec(0..16, |x| x);
1483
1484 assert_eq!(policy_len(&strategy), 1);
1485 assert_eq!(policy_len(&other), 0);
1486 }
1487
1488 #[test]
1493 fn spawn_driven_inline_on_member_thread() {
1494 let pool = ThreadPoolBuilder::new()
1495 .num_threads(2)
1496 .use_current_thread()
1497 .spawn_handler(|_| Ok(()))
1498 .build()
1499 .unwrap();
1500 let strategy = Rayon::with_pool(Arc::new(pool));
1501
1502 let result = strategy
1503 .spawn(2, |strategy| strategy.map_collect_vec(0..2, |i| i + 1))
1504 .now_or_never()
1505 .expect("spawn should complete on first poll via the yield loop");
1506 assert_eq!(result, vec![1, 2]);
1507 }
1508
1509 #[test]
1510 fn with_parallelism_overrides_planning_parallelism() {
1511 let strategy = Rayon::new(NonZeroUsize::new(1).unwrap())
1512 .unwrap()
1513 .with_parallelism(NonZeroUsize::new(4).unwrap());
1514 let strategy = strategy.manual();
1515 assert_eq!(strategy.parallelism(), 4);
1516 assert_eq!(strategy.run(2, || "serial", || "parallel"), "parallel");
1517 }
1518
1519 #[test]
1520 fn adaptive_policy_is_shared_by_clones() {
1521 let strategy = parallel_strategy();
1522 let clone = strategy.clone();
1523
1524 let _: Vec<_> = clone.map_collect_vec(0..16, |x| x);
1525
1526 assert_eq!(policy_len(&strategy), 1);
1527 assert_eq!(policy_len(&clone), 1);
1528 }
1529
1530 #[test]
1531 fn adaptive_policy_records_all_adaptive_operations() {
1532 let strategy = parallel_strategy();
1533
1534 let _: Vec<_> = strategy.fold_init(
1535 0..16,
1536 || (),
1537 Vec::new,
1538 |mut acc, _, x| {
1539 acc.push(x);
1540 acc
1541 },
1542 |mut a, b| {
1543 a.extend(b);
1544 a
1545 },
1546 );
1547 let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
1548 let _: Result<i32, ()> = strategy.try_fold(0..16, || 0, |acc, x| Ok(acc + x), |a, b| a + b);
1549 let _: Vec<_> = strategy.map_collect_vec(0..16, |x| x);
1550 let _: Result<Vec<_>, ()> = strategy.try_map_collect_vec(0..16, Ok);
1551 let _: Vec<_> = strategy.map_init_collect_vec(
1552 0..16,
1553 || AtomicUsize::new(0),
1554 |counter, x| {
1555 counter.fetch_add(1, Ordering::Relaxed);
1556 x
1557 },
1558 );
1559 let _: Vec<_> = strategy.map_init_collect_vec_with_multiplier(
1560 0..16,
1561 2,
1562 || AtomicUsize::new(0),
1563 |counter, x| {
1564 counter.fetch_add(1, Ordering::Relaxed);
1565 x
1566 },
1567 );
1568 let _: usize = strategy.run(16, || 1, || 2);
1569 let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..16, |x| {
1570 if x % 2 == 0 { (x, Some(x)) } else { (x, None) }
1571 });
1572 let _: (i32, i32) = strategy.join(|| 1, || 2);
1573 let mut sortable = vec![3, 2, 1];
1574 strategy.sort_by(&mut sortable, |a, b| a.cmp(b));
1575
1576 assert_eq!(sortable, vec![1, 2, 3]);
1577 assert_eq!(policy_len(&strategy), 10);
1578 }
1579
1580 #[test]
1581 fn adaptive_policy_buckets_by_input_size() {
1582 let strategy = parallel_strategy();
1583
1584 map_from_same_callsite(&strategy, 1);
1585 map_from_same_callsite(&strategy, 2);
1586 map_from_same_callsite(&strategy, 3);
1587
1588 assert_eq!(policy_len(&strategy), 2);
1589 }
1590
1591 #[test]
1592 fn adaptive_policy_buckets_by_work_multiplier() {
1593 let strategy = parallel_strategy();
1594
1595 map_init_with_multiplier_from_same_callsite(&strategy, 16, 1);
1596 map_init_with_multiplier_from_same_callsite(&strategy, 16, 2);
1597 map_init_with_multiplier_from_same_callsite(&strategy, 16, 3);
1598
1599 assert_eq!(policy_len(&strategy), 2);
1600 }
1601
1602 #[test]
1603 fn adaptive_run_buckets_by_input_size() {
1604 let strategy = parallel_strategy();
1605
1606 run_from_same_callsite(&strategy, 1);
1607 run_from_same_callsite(&strategy, 2);
1608 run_from_same_callsite(&strategy, 3);
1609
1610 assert_eq!(policy_len(&strategy), 2);
1611 }
1612
1613 #[test]
1616 fn manual_spawn_always_hands_off() {
1617 let strategy = parallel_strategy();
1618 let manual = strategy.manual();
1619
1620 for _ in 0..10 {
1621 let on_pool = futures::executor::block_on(
1622 manual.spawn(1, |_| rayon::current_thread_index().is_some()),
1623 );
1624 assert!(on_pool, "manual spawn ran on the calling task");
1625 }
1626 }
1627
1628 #[test]
1629 fn manual_strategy_does_not_use_adaptive_policy() {
1630 let strategy = parallel_strategy();
1631 let manual = strategy.manual();
1632
1633 let _: usize = manual.fold(0..4, || 0, |acc, x| acc + x, |a, b| a + b);
1634 assert_eq!(manual.run(4, || 1, || 2), 2);
1635
1636 assert_eq!(policy_len(&strategy), 0);
1637 assert_eq!(policy_len(&manual.strategy), 0);
1638 }
1639
1640 #[test]
1641 fn sequential_run_uses_serial_body() {
1642 assert_eq!(Sequential.run(4, || 1, || 2), 1);
1643 }
1644
1645 #[test]
1646 fn adaptive_policy_keys_default_methods_by_external_callsite() {
1647 let strategy = parallel_strategy();
1648
1649 let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
1653 let _: i32 = strategy.fold(0..16, || 0, |acc, x| acc + x, |a, b| a + b);
1654
1655 assert_eq!(policy_len(&strategy), 2);
1656 }
1657
1658 #[test]
1659 fn adaptive_policy_keys_partition_map_by_external_callsite() {
1660 let strategy = parallel_strategy();
1661
1662 map_partition_from_same_callsite(&strategy, 16);
1663 let _: (Vec<_>, Vec<_>) = strategy.map_partition_collect_vec(0..16, |x| {
1664 if x % 2 == 0 { (x, Some(x)) } else { (x, None) }
1665 });
1666
1667 assert_eq!(policy_len(&strategy), 2);
1668 }
1669
1670 #[test]
1671 fn join_does_not_use_adaptive_policy() {
1672 let strategy = parallel_strategy();
1673
1674 let result = strategy.join(|| 1, || 2);
1675
1676 assert_eq!(result, (1, 2));
1677 assert_eq!(policy_len(&strategy), 0);
1678 }
1679
1680 #[test]
1681 fn sequential_spawn_runs_job() {
1682 let result = futures::executor::block_on(Sequential.spawn(1, |_| 7));
1683
1684 assert_eq!(result, 7);
1685 }
1686
1687 #[test]
1688 fn rayon_spawn_runs_job_on_pool() {
1689 let strategy = parallel_strategy();
1690
1691 let result = futures::executor::block_on(strategy.spawn(1, |_| {
1692 assert!(rayon::current_thread_index().is_some());
1693 7
1694 }));
1695
1696 assert_eq!(result, 7);
1697
1698 assert_eq!(policy_len(&strategy), 0);
1700 }
1701
1702 #[test]
1703 fn rayon_spawn_runs_inline_on_current_thread_single_worker_pool() {
1704 let pool = ThreadPoolBuilder::new()
1705 .num_threads(1)
1706 .use_current_thread()
1707 .build()
1708 .unwrap();
1709 let strategy =
1710 Rayon::with_pool(Arc::new(pool)).with_parallelism(NonZeroUsize::new(4).unwrap());
1711
1712 assert_eq!(strategy.manual().parallelism(), 4);
1713
1714 let result = strategy.spawn(1, |_| 7).now_or_never();
1715
1716 assert_eq!(result, Some(7));
1717 assert_eq!(policy_len(&strategy), 0);
1718 }
1719
1720 #[test]
1721 #[should_panic(expected = "boom")]
1722 fn rayon_spawn_propagates_job_panic() {
1723 let strategy = parallel_strategy();
1725
1726 let _: () = futures::executor::block_on(strategy.spawn(1, |_| panic!("boom")));
1727 }
1728
1729 #[test]
1730 #[should_panic(expected = "boom")]
1731 fn sequential_spawn_propagates_job_panic() {
1732 let _: () = futures::executor::block_on(Sequential.spawn(1, |_| panic!("boom")));
1733 }
1734
1735 proptest! {
1736 #[test]
1737 fn parallel_fold_init_matches_sequential(data in prop::collection::vec(any::<i32>(), 0..500)) {
1738 let sequential = Sequential;
1739 let parallel = parallel_strategy();
1740
1741 let seq_result: Vec<i32> = sequential.fold_init(
1742 &data,
1743 || (),
1744 Vec::new,
1745 |mut acc, _, &x| { acc.push(x.wrapping_mul(2)); acc },
1746 |mut a, b| { a.extend(b); a },
1747 );
1748
1749 let par_result: Vec<i32> = parallel.fold_init(
1750 &data,
1751 || (),
1752 Vec::new,
1753 |mut acc, _, &x| { acc.push(x.wrapping_mul(2)); acc },
1754 |mut a, b| { a.extend(b); a },
1755 );
1756
1757 prop_assert_eq!(seq_result, par_result);
1758 }
1759
1760 #[test]
1761 fn fold_equals_fold_init(data in prop::collection::vec(any::<i32>(), 0..500)) {
1762 let s = Sequential;
1763
1764 let via_fold: Vec<i32> = s.fold(
1765 &data,
1766 Vec::new,
1767 |mut acc, &x| { acc.push(x); acc },
1768 |mut a, b| { a.extend(b); a },
1769 );
1770
1771 let via_fold_init: Vec<i32> = s.fold_init(
1772 &data,
1773 || (),
1774 Vec::new,
1775 |mut acc, _, &x| { acc.push(x); acc },
1776 |mut a, b| { a.extend(b); a },
1777 );
1778
1779 prop_assert_eq!(via_fold, via_fold_init);
1780 }
1781
1782 #[test]
1783 fn parallel_try_fold_matches_sequential(data in prop::collection::vec(any::<i32>(), 0..500)) {
1784 let sequential: Result<i32, ()> = Sequential.try_fold(
1785 &data,
1786 || 0i32,
1787 |acc, &x| Ok(acc.wrapping_add(x)),
1788 |a, b| a.wrapping_add(b),
1789 );
1790 let parallel: Result<i32, ()> = parallel_strategy().try_fold(
1791 &data,
1792 || 0i32,
1793 |acc, &x| Ok(acc.wrapping_add(x)),
1794 |a, b| a.wrapping_add(b),
1795 );
1796
1797 prop_assert_eq!(sequential, parallel);
1798 }
1799
1800 #[test]
1801 fn map_collect_vec_equals_fold(data in prop::collection::vec(any::<i32>(), 0..500)) {
1802 let s = Sequential;
1803 let map_op = |&x: &i32| x.wrapping_mul(3);
1804
1805 let via_map: Vec<i32> = s.map_collect_vec(&data, map_op);
1806
1807 let via_fold: Vec<i32> = s.fold(
1808 &data,
1809 Vec::new,
1810 |mut acc, item| { acc.push(map_op(item)); acc },
1811 |mut a, b| { a.extend(b); a },
1812 );
1813
1814 prop_assert_eq!(via_map, via_fold);
1815 }
1816
1817 #[test]
1818 fn try_map_collect_vec_collects_successes(data in prop::collection::vec(any::<i32>(), 0..500)) {
1819 let expected: Vec<i32> = data.iter().map(|x| x.wrapping_mul(5)).collect();
1820
1821 let sequential: Result<Vec<i32>, ()> =
1822 Sequential.try_map_collect_vec(&data, |&x| Ok(x.wrapping_mul(5)));
1823 prop_assert_eq!(sequential, Ok(expected.clone()));
1824
1825 let parallel: Result<Vec<i32>, ()> =
1826 parallel_strategy().try_map_collect_vec(&data, |&x| Ok(x.wrapping_mul(5)));
1827 prop_assert_eq!(parallel, Ok(expected));
1828 }
1829
1830 #[test]
1831 fn try_map_collect_vec_returns_first_error(data in prop::collection::vec(any::<i32>(), 0..500)) {
1832 let expected_error = data.iter().position(|x| x % 7 == 0);
1833 let result: Result<Vec<i32>, usize> =
1834 Sequential.try_map_collect_vec(data.iter().enumerate(), |(i, &x)| {
1835 if x % 7 == 0 {
1836 Err(i)
1837 } else {
1838 Ok(x)
1839 }
1840 });
1841
1842 match expected_error {
1843 Some(i) => prop_assert_eq!(result, Err(i)),
1844 None => prop_assert_eq!(result, Ok(data)),
1845 }
1846 }
1847
1848 #[test]
1849 fn map_init_collect_vec_equals_fold_init(data in prop::collection::vec(any::<i32>(), 0..500)) {
1850 let s = Sequential;
1851
1852 let via_map: Vec<i32> = s.map_init_collect_vec(
1853 &data,
1854 || 0i32,
1855 |counter, &x| { *counter += 1; x.wrapping_add(*counter) },
1856 );
1857
1858 let via_fold_init: Vec<i32> = s.fold_init(
1859 &data,
1860 || 0i32,
1861 Vec::new,
1862 |mut acc, counter, &x| {
1863 *counter += 1;
1864 acc.push(x.wrapping_add(*counter));
1865 acc
1866 },
1867 |mut a, b| { a.extend(b); a },
1868 );
1869
1870 prop_assert_eq!(via_map, via_fold_init);
1871 }
1872
1873 #[test]
1874 fn map_partition_collect_vec_returns_valid_results(data in prop::collection::vec(any::<i32>(), 0..500)) {
1875 let s = Sequential;
1876
1877 let map_op = |&x: &i32| {
1878 let value = if x % 2 == 0 { Some(x.wrapping_mul(2)) } else { None };
1879 (x, value)
1880 };
1881
1882 let (results, filtered) = s.map_partition_collect_vec(data.iter(), map_op);
1883
1884 let expected_results: Vec<i32> = data.iter().filter(|&&x| x % 2 == 0).map(|&x| x.wrapping_mul(2)).collect();
1886 prop_assert_eq!(results, expected_results);
1887
1888 let expected_filtered: Vec<i32> = data.iter().filter(|&&x| x % 2 != 0).copied().collect();
1890 prop_assert_eq!(filtered, expected_filtered);
1891 }
1892 }
1893
1894 #[test]
1895 fn try_map_collect_vec_sequential_short_circuits() {
1896 let calls = AtomicUsize::new(0);
1897 let result: Result<Vec<usize>, usize> = Sequential.try_map_collect_vec(0..10, |i| {
1898 calls.fetch_add(1, Ordering::Relaxed);
1899 if i == 3 { Err(i) } else { Ok(i) }
1900 });
1901
1902 assert_eq!(result, Err(3));
1903 assert_eq!(calls.load(Ordering::Relaxed), 4);
1904 }
1905
1906 #[test]
1907 fn try_map_collect_vec_parallel_returns_an_error() {
1908 let result: Result<Vec<usize>, usize> = parallel_strategy()
1909 .try_map_collect_vec(0..128, |i| if i == 17 || i == 42 { Err(i) } else { Ok(i) });
1910
1911 assert!(matches!(result, Err(17 | 42)));
1912 }
1913}