molframe_core/parallel/
execute.rs1use super::plan::BlockPlan;
8use crate::ExecutionContext;
9use rayon::prelude::*;
10use std::fmt;
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::sync::{Arc, Mutex, OnceLock};
13
14#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub struct WorkerPanicked;
21
22impl fmt::Display for WorkerPanicked {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 formatter.write_str("a worker thread panicked")
25 }
26}
27
28impl std::error::Error for WorkerPanicked {}
29
30#[cfg(test)]
44pub(crate) fn map_blocks<T, F>(
45 plan: BlockPlan,
46 workers: usize,
47 block: F,
48) -> Result<Vec<T>, WorkerPanicked>
49where
50 T: Send,
51 F: Fn(usize, std::ops::Range<usize>) -> T + Sync,
52{
53 SharedPool::global().map(plan, workers, block)
54}
55
56pub fn map_blocks_in<T, F>(
66 plan: BlockPlan,
67 context: &ExecutionContext,
68 block: F,
69) -> Result<Vec<T>, WorkerPanicked>
70where
71 T: Send,
72 F: Fn(usize, std::ops::Range<usize>) -> T + Sync,
73{
74 context.executor().map(plan, context.worker_budget(), block)
75}
76
77pub(crate) struct SharedPool {
78 pub(super) pool: Option<rayon::ThreadPool>,
79 capacity: usize,
80}
81
82impl fmt::Debug for SharedPool {
83 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
84 formatter
85 .debug_struct("SharedPool")
86 .field("capacity", &self.capacity)
87 .finish_non_exhaustive()
88 }
89}
90
91impl SharedPool {
92 pub(crate) fn global() -> Arc<Self> {
93 static SHARED: OnceLock<Arc<SharedPool>> = OnceLock::new();
94 Arc::clone(SHARED.get_or_init(|| Arc::new(Self::build())))
95 }
96
97 fn build() -> Self {
98 let requested = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
99 let pool = rayon::ThreadPoolBuilder::new()
100 .num_threads(requested)
101 .thread_name(|index| format!("molframe-worker-{index}"))
102 .build()
103 .ok();
104 let capacity = pool
105 .as_ref()
106 .map_or(1, rayon::ThreadPool::current_num_threads);
107 Self { pool, capacity }
108 }
109
110 pub(crate) const fn capacity(&self) -> usize {
111 self.capacity
112 }
113
114 pub(crate) fn map<T, F>(
115 &self,
116 plan: BlockPlan,
117 workers: usize,
118 block: F,
119 ) -> Result<Vec<T>, WorkerPanicked>
120 where
121 T: Send,
122 F: Fn(usize, std::ops::Range<usize>) -> T + Sync,
123 {
124 let mut output = Vec::new();
125 let window = plan
126 .useful_workers(workers.min(self.capacity))
127 .saturating_mul(2);
128 for start in (0..plan.blocks()).step_by(window) {
129 output.extend(self.map_window(
130 plan,
131 start..start.saturating_add(window).min(plan.blocks()),
132 workers,
133 &block,
134 )?);
135 }
136 Ok(output)
137 }
138
139 pub(crate) fn map_window<T, F>(
140 &self,
141 plan: BlockPlan,
142 indices: std::ops::Range<usize>,
143 workers: usize,
144 block: &F,
145 ) -> Result<Vec<T>, WorkerPanicked>
146 where
147 T: Send,
148 F: Fn(usize, std::ops::Range<usize>) -> T + Sync,
149 {
150 let workers = workers.max(1).min(self.capacity).min(indices.len());
151 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
152 if workers <= 1 || self.pool.is_none() || rayon::current_thread_index().is_some() {
153 return indices
154 .filter_map(|index| plan.range(index).map(|range| block(index, range)))
155 .collect();
156 }
157 let slots: Vec<_> = indices.clone().map(|_| Mutex::new(None)).collect();
158 let next = AtomicUsize::new(indices.start);
159 if let Some(pool) = &self.pool {
160 pool.install(|| {
161 (0..workers).into_par_iter().for_each(|_| {
162 loop {
163 let index = next.fetch_add(1, Ordering::Relaxed);
164 if index >= indices.end {
165 break;
166 }
167 let Some(range) = plan.range(index) else {
168 break;
169 };
170 let value = block(index, range);
171 let mut slot = match slots[index - indices.start].lock() {
172 Ok(slot) => slot,
173 Err(poisoned) => poisoned.into_inner(),
174 };
175 *slot = Some(value);
176 }
177 });
178 });
179 }
180 slots
181 .into_iter()
182 .filter_map(|slot| match slot.into_inner() {
183 Ok(value) => value,
184 Err(poisoned) => poisoned.into_inner(),
185 })
186 .collect()
187 }))
188 .map_err(|_| WorkerPanicked)
189 }
190}
191
192#[cfg(test)]
193#[path = "execute_tests.rs"]
194mod tests;