1pub use rayon::prelude::*;
7
8use crate::error::{OcasError, Result};
9
10pub struct ThreadPool {
28 inner: rayon::ThreadPool,
29}
30
31impl ThreadPool {
32 pub fn new(threads: usize) -> Result<Self> {
40 let inner = rayon::ThreadPoolBuilder::new()
41 .num_threads(threads)
42 .build()
43 .map_err(|e| OcasError::BackendError {
44 backend: "rayon".into(),
45 message: e.to_string(),
46 })?;
47 Ok(Self { inner })
48 }
49
50 pub fn install<F, R>(&self, op: F) -> R
52 where
53 F: FnOnce() -> R + Send,
54 R: Send,
55 {
56 self.inner.install(op)
57 }
58
59 pub fn current_num_threads(&self) -> usize {
61 self.inner.current_num_threads()
62 }
63}
64
65pub fn init(threads: usize) -> Result<()> {
75 rayon::ThreadPoolBuilder::new()
76 .num_threads(threads)
77 .build_global()
78 .map_err(|e| OcasError::BackendError {
79 backend: "rayon".into(),
80 message: e.to_string(),
81 })
82}
83
84pub fn install<F, R>(op: F) -> R
95where
96 F: FnOnce() -> R + Send,
97 R: Send,
98{
99 rayon::join(|| {}, op).1
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 mod simple {
107 use super::*;
108
109 #[test]
110 #[cfg(not(miri))]
111 fn global_pool_install() {
112 let result = install(|| 21 + 21);
113 assert_eq!(result, 42);
114 }
115
116 #[test]
117 #[cfg(not(miri))]
118 fn thread_pool_creation_and_size() {
119 let pool = ThreadPool::new(2).expect("should create a 2-thread pool");
120 assert_eq!(pool.current_num_threads(), 2);
121 }
122 }
123
124 mod medium {
125 use super::*;
126
127 #[test]
128 #[cfg(not(miri))]
129 fn custom_thread_pool_join() {
130 let pool = ThreadPool::new(2).expect("should create a 2-thread pool");
131 let result = pool.install(|| {
132 let (a, b) = rayon::join(|| 1 + 2, || 3 + 4);
133 a + b
134 });
135 assert_eq!(result, 10);
136 }
137
138 #[test]
139 #[cfg(not(miri))]
140 fn thread_pool_parallel_sum() {
141 let pool = ThreadPool::new(2).expect("should create a 2-thread pool");
142 let data: Vec<i64> = (0..1_000).collect();
143 let sum = pool.install(|| data.into_par_iter().sum::<i64>());
144 assert_eq!(sum, 499_500);
145 }
146 }
147
148 mod complex {
149 use super::*;
150
151 #[test]
152 #[cfg(not(miri))]
153 fn thread_pool_maps_error_to_ocas_error() {
154 init(2).ok();
158 let err = init(4).expect_err("reinitializing global pool should fail");
159 assert_eq!(
160 err,
161 OcasError::BackendError {
162 backend: "rayon".into(),
163 message: "The global thread pool has already been initialized.".into(),
164 }
165 );
166 }
167
168 #[test]
169 #[cfg(not(miri))]
170 fn nested_pools_run_on_different_threads() {
171 let outer = ThreadPool::new(2).expect("outer pool");
172 let result = outer.install(|| {
173 let inner = ThreadPool::new(2).expect("inner pool");
174 inner.install(|| {
175 let (a, b) = rayon::join(|| 1 + 1, || 2 + 2);
176 a + b
177 })
178 });
179 assert_eq!(result, 6);
180 }
181 }
182
183 mod extreme {
184 use super::*;
185
186 #[test]
187 #[cfg(not(miri))]
188 fn stress_many_parallel_tasks() {
189 let pool = ThreadPool::new(4).expect("should create a 4-thread pool");
190 let result = pool.install(|| (0..10_000).into_par_iter().map(|x| x * x).sum::<i64>());
191 let n = 10_000i64;
193 assert_eq!(result, (n - 1) * n * (2 * n - 1) / 6);
194 }
195 }
196}