1use crate::domain::{Domain, DomainBusy, LocalMode};
2use crate::map::{map, MapError};
3use rayon::iter::{IntoParallelIterator, ParallelIterator};
4use std::fmt;
5
6#[derive(Debug)]
8pub enum MapInError {
9 MissingPool,
11 ForeignPool,
13 DomainBusy(DomainBusy),
15 Callback(MapError),
17}
18
19impl fmt::Display for MapInError {
20 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Self::MissingPool => formatter.write_str("domain has no Rayon pool"),
23 Self::ForeignPool => formatter.write_str("map_in called from a foreign Rayon pool"),
24 Self::DomainBusy(error) => error.fmt(formatter),
25 Self::Callback(error) => error.fmt(formatter),
26 }
27 }
28}
29
30impl std::error::Error for MapInError {
31 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32 match self {
33 Self::DomainBusy(error) => Some(error),
34 Self::Callback(error) => Some(error),
35 Self::MissingPool | Self::ForeignPool => None,
36 }
37 }
38}
39
40impl From<DomainBusy> for MapInError {
41 fn from(error: DomainBusy) -> Self {
42 Self::DomainBusy(error)
43 }
44}
45
46impl From<MapError> for MapInError {
47 fn from(error: MapError) -> Self {
48 Self::Callback(error)
49 }
50}
51
52pub(crate) fn in_rayon_worker_context() -> bool {
53 rayon::current_thread_index().is_some()
54}
55
56pub(crate) fn run_in_current_pool<T, U, E, F>(
57 mode: LocalMode,
58 items: Vec<T>,
59 f: &F,
60) -> Result<Vec<U>, MapError>
61where
62 T: Send,
63 U: Send,
64 E: fmt::Display + Send,
65 F: Fn(T) -> Result<U, E> + Sync,
66{
67 match mode {
68 LocalMode::Sequential | LocalMode::Inner => map(items, f),
69 LocalMode::Outer => {
70 let results: Vec<Result<U, E>> = items.into_par_iter().map(f).collect();
71 map(results, |result| result)
72 }
73 }
74}
75
76pub(crate) fn run_in_pool<T, U, E, F>(
77 pool: &rayon::ThreadPool,
78 mode: LocalMode,
79 items: Vec<T>,
80 f: F,
81) -> Result<Vec<U>, MapError>
82where
83 T: Send,
84 U: Send,
85 E: fmt::Display + Send,
86 F: Fn(T) -> Result<U, E> + Send + Sync,
87{
88 pool.install(|| run_in_current_pool(mode, items, &f))
89}
90
91pub fn map_in<T, U, E, F>(
123 domain: &Domain,
124 mode: LocalMode,
125 items: Vec<T>,
126 f: F,
127) -> Result<Vec<U>, MapInError>
128where
129 T: Send,
130 U: Send,
131 E: fmt::Display + Send,
132 F: Fn(T) -> Result<U, E> + Send + Sync,
133{
134 let pool = domain.rayon_pool().ok_or(MapInError::MissingPool)?;
135 if in_rayon_worker_context() && pool.current_thread_index().is_none() {
136 return Err(MapInError::ForeignPool);
137 }
138 let _admission = domain.try_admit().map_err(MapInError::DomainBusy)?;
139
140 run_in_pool(pool, mode, items, f).map_err(MapInError::Callback)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::{in_rayon_worker_context, map_in, MapInError};
146 use crate::{Domain, DomainBusy, LocalMode};
147 use rayon::ThreadPoolBuilder;
148 use std::error::Error;
149 use std::panic::{catch_unwind, AssertUnwindSafe};
150 use std::sync::atomic::{AtomicUsize, Ordering};
151 use std::sync::{mpsc, Arc, Once};
152
153 fn domain(worker_count: usize) -> (Domain, Arc<rayon::ThreadPool>) {
154 let pool = Arc::new(
155 ThreadPoolBuilder::new()
156 .num_threads(worker_count)
157 .build()
158 .unwrap(),
159 );
160 let cpus = (0..worker_count).collect();
161 let domain = Domain::external(Arc::clone(&pool), cpus, worker_count).unwrap();
162 (domain, pool)
163 }
164
165 #[test]
166 fn rayon_worker_guard_matches_entry_contexts() {
167 assert!(!in_rayon_worker_context());
168 assert!(!std::thread::spawn(in_rayon_worker_context).join().unwrap());
169
170 let pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
171 assert!(pool.install(in_rayon_worker_context));
172 assert!(pool.scope(|_| in_rayon_worker_context()));
173 assert!(!pool.in_place_scope(|_| in_rayon_worker_context()));
174
175 let (sender, receiver) = mpsc::channel();
176 rayon::scope(|scope| {
177 assert!(in_rayon_worker_context());
178 scope.spawn(|_| sender.send(in_rayon_worker_context()).unwrap());
179 });
180 assert!(receiver.recv().unwrap());
181 }
182
183 #[test]
184 fn current_pool_runner_accepts_borrowed_callback() {
185 let (domain, pool) = domain(1);
186 let bias = 10;
187 let values = [1, 2, 3];
188 let items = values.iter().collect::<Vec<_>>();
189 let callback = |value: &i32| Ok::<_, &'static str>(*value + bias);
190 let result =
191 pool.install(|| super::run_in_current_pool(LocalMode::Sequential, items, &callback));
192 assert_eq!(result.unwrap(), vec![11, 12, 13]);
193 drop(domain);
194 }
195
196 #[test]
197 fn all_modes_succeed_in_order() {
198 let (domain, _) = domain(2);
199 for mode in [LocalMode::Sequential, LocalMode::Inner, LocalMode::Outer] {
200 let result = map_in(&domain, mode, vec![1, 2, 3], |item| {
201 Ok::<_, &'static str>(item * 2)
202 })
203 .unwrap();
204 assert_eq!(result, vec![2, 4, 6]);
205 }
206 }
207
208 #[test]
209 fn sequential_and_inner_stop_at_first_error() {
210 for mode in [LocalMode::Sequential, LocalMode::Inner] {
211 let (domain, _) = domain(2);
212 let calls = Arc::new(AtomicUsize::new(0));
213 let seen = Arc::clone(&calls);
214 let error = map_in(&domain, mode, vec![0, 1, 2, 3], move |item| {
215 seen.fetch_add(1, Ordering::Relaxed);
216 if item == 2 {
217 Err::<usize, _>("callback failed")
218 } else {
219 Ok(item)
220 }
221 })
222 .unwrap_err();
223 assert!(matches!(error, MapInError::Callback(ref error) if error.index() == 2));
224 assert_eq!(calls.load(Ordering::Relaxed), 3);
225 }
226 }
227
228 #[test]
229 fn outer_evaluates_every_input_and_reports_lowest_error() {
230 let (domain, _) = domain(2);
231 let calls: Arc<Vec<AtomicUsize>> = Arc::new((0..6).map(|_| AtomicUsize::new(0)).collect());
232 let seen = Arc::clone(&calls);
233 let error = map_in(&domain, LocalMode::Outer, (0..6).collect(), move |item| {
234 seen[item].fetch_add(1, Ordering::Relaxed);
235 match item {
236 1 => Err::<usize, _>("first"),
237 4 => Err::<usize, _>("later"),
238 _ => Ok(item),
239 }
240 })
241 .unwrap_err();
242 assert!(matches!(error, MapInError::Callback(ref error) if error.index() == 1));
243 assert_eq!(error.to_string(), "map failed at index 1: first");
244 assert!(calls.iter().all(|count| count.load(Ordering::Relaxed) == 1));
245 }
246
247 #[test]
248 fn preconditions_have_fixed_order_and_never_call_back() {
249 let calls = Arc::new(AtomicUsize::new(0));
250 let sequential = Domain::sequential();
251 let admission = sequential.try_admit().unwrap();
252 let seen = Arc::clone(&calls);
253 assert!(matches!(
254 map_in(&sequential, LocalMode::Sequential, vec![()], move |_| {
255 seen.fetch_add(1, Ordering::Relaxed);
256 Ok::<_, &'static str>(())
257 }),
258 Err(MapInError::MissingPool)
259 ));
260 drop(admission);
261
262 let (domain, pool) = domain(1);
263 let foreign_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
264 let admission = domain.try_admit().unwrap();
265 let seen = Arc::clone(&calls);
266 let foreign_and_busy = foreign_pool.install(|| {
267 map_in(&domain, LocalMode::Sequential, vec![()], move |_| {
268 seen.fetch_add(1, Ordering::Relaxed);
269 Ok::<_, &'static str>(())
270 })
271 });
272 assert!(matches!(foreign_and_busy, Err(MapInError::ForeignPool)));
273 drop(admission);
274
275 let admission = domain.try_admit().unwrap();
276 let seen = Arc::clone(&calls);
277 let busy = pool.install(|| {
278 map_in(&domain, LocalMode::Sequential, vec![()], move |_| {
279 seen.fetch_add(1, Ordering::Relaxed);
280 Ok::<_, &'static str>(())
281 })
282 });
283 assert!(matches!(busy, Err(MapInError::DomainBusy(_))));
284 drop(admission);
285 assert_eq!(calls.load(Ordering::Relaxed), 0);
286 }
287
288 #[test]
289 fn foreign_custom_and_global_pools_are_rejected_but_same_pool_is_accepted() {
290 let (domain, pool) = domain(1);
291 let foreign_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
292 let custom_result = foreign_pool.install(|| {
293 map_in(&domain, LocalMode::Sequential, vec![1], |_| {
294 Ok::<_, &'static str>(1)
295 })
296 });
297 assert!(matches!(custom_result, Err(MapInError::ForeignPool)));
298
299 let same_pool_result = pool.install(|| {
300 map_in(&domain, LocalMode::Sequential, vec![1], |_| {
301 Ok::<_, &'static str>(1)
302 })
303 });
304 assert_eq!(same_pool_result.unwrap(), vec![1]);
305
306 static GLOBAL: Once = Once::new();
307 GLOBAL.call_once(|| {
308 let _ = ThreadPoolBuilder::new().num_threads(2).build_global();
309 });
310 let (sender, receiver) = mpsc::channel();
311 rayon::scope(|scope| {
312 scope.spawn(|_| {
313 sender
314 .send(map_in(&domain, LocalMode::Sequential, vec![1], |_| {
315 Ok::<_, &'static str>(1)
316 }))
317 .unwrap();
318 });
319 });
320 assert!(matches!(
321 receiver.recv().unwrap(),
322 Err(MapInError::ForeignPool)
323 ));
324 }
325
326 #[test]
327 fn inner_callback_can_use_the_target_pool() {
328 let (domain, _) = domain(2);
329 let result = map_in(&domain, LocalMode::Inner, vec![()], |_| {
330 let (thread_count, in_pool) = rayon::join(rayon::current_num_threads, || {
331 rayon::current_thread_index().is_some()
332 });
333 Ok::<_, &'static str>((thread_count, in_pool))
334 })
335 .unwrap();
336 assert_eq!(result, vec![(2, true)]);
337 }
338
339 #[test]
340 fn admission_is_released_after_success_error_and_unwind() {
341 let (domain, _) = domain(1);
342 map_in(&domain, LocalMode::Sequential, vec![()], |_| {
343 Ok::<_, &'static str>(())
344 })
345 .unwrap();
346 assert!(domain.try_admit().is_ok());
347
348 let error = map_in(&domain, LocalMode::Sequential, vec![()], |_| {
349 Err::<(), _>("callback failed")
350 });
351 assert!(error.is_err());
352 assert!(domain.try_admit().is_ok());
353
354 let result = catch_unwind(AssertUnwindSafe(|| {
355 map_in(
356 &domain,
357 LocalMode::Sequential,
358 vec![()],
359 |_| -> Result<(), &'static str> {
360 panic!("callback panic");
361 },
362 )
363 .unwrap();
364 }));
365 assert!(result.is_err());
366 assert!(domain.try_admit().is_ok());
367 }
368
369 #[test]
370 fn borrowed_inputs_and_sync_stack_capture_need_no_static_bound() {
371 let (domain, _) = domain(2);
372 let values = [1, 2, 3];
373 let bias = 10;
374 let captured = &bias;
375 let items = values.iter().collect::<Vec<_>>();
376 let result = map_in(&domain, LocalMode::Outer, items, |value| {
377 Ok::<_, &'static str>(*value + *captured)
378 })
379 .unwrap();
380 assert_eq!(result, vec![11, 12, 13]);
381 }
382
383 #[test]
384 fn error_sources_and_conversions_are_typed() {
385 let busy: MapInError = DomainBusy.into();
386 assert!(busy.source().is_some());
387 let map_error = crate::map::map(vec![()], |_| Err::<(), _>("bad")).unwrap_err();
388 let callback: MapInError = map_error.into();
389 assert!(callback.source().is_some());
390 }
391}