Skip to main content

orx_parallel/pools/pool_impl/
basic.rs

1use crate::NumThreads;
2use crate::pools::ThreadPool;
3use crate::pools::env::max_num_threads_by_env_and_resource;
4use crate::pools::scope::Scope;
5use core::num::NonZeroUsize;
6use std::any::Any;
7use std::boxed::Box;
8use std::collections::VecDeque;
9use std::marker::PhantomData;
10use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::sync::{Arc, Condvar, Mutex};
13use std::thread;
14use std::vec::Vec;
15
16struct Inner {
17    shared: Arc<WorkerShared>,
18    workers: Mutex<Vec<std::thread::JoinHandle<()>>>,
19}
20
21struct WorkerShared {
22    state: Mutex<WorkerState>,
23    cv: Condvar,
24}
25
26struct WorkerState {
27    shutdown: bool,
28    queue: VecDeque<Task>,
29}
30
31impl Drop for Inner {
32    fn drop(&mut self) {
33        {
34            let mut state = self.shared.state.lock().expect("poisoned pool lock");
35            state.shutdown = true;
36            while let Some(task) = state.queue.pop_front() {
37                unsafe { task.drop() };
38            }
39        }
40        self.shared.cv.notify_all();
41
42        let mut workers = self.workers.lock().expect("poisoned workers lock");
43        for worker in workers.drain(..) {
44            let _ = worker.join();
45        }
46    }
47}
48
49struct ScopeRuntime {
50    pending: AtomicUsize,
51    completion_lock: Mutex<()>,
52    completion_cv: Condvar,
53    panic: Mutex<Option<Box<dyn Any + Send>>>,
54}
55
56impl ScopeRuntime {
57    fn new() -> Self {
58        Self {
59            pending: AtomicUsize::new(0),
60            completion_lock: Mutex::new(()),
61            completion_cv: Condvar::new(),
62            panic: Mutex::new(None),
63        }
64    }
65
66    fn begin_task(&self) {
67        self.pending.fetch_add(1, Ordering::AcqRel);
68    }
69
70    fn complete_task(&self) {
71        // Decrement and notify under the lock so the main thread cannot exit
72        // wait_for_completion (and free this ScopeRuntime) while we still hold a
73        // reference to completion_lock / completion_cv.
74        let guard = self
75            .completion_lock
76            .lock()
77            .expect("poisoned scope completion lock");
78        let prev = self.pending.fetch_sub(1, Ordering::AcqRel);
79        if prev == 1 {
80            self.completion_cv.notify_all();
81        }
82        drop(guard);
83    }
84
85    fn wait_for_completion(&self) {
86        let mut guard = self
87            .completion_lock
88            .lock()
89            .expect("poisoned scope completion lock");
90        while self.pending.load(Ordering::Acquire) != 0 {
91            guard = self
92                .completion_cv
93                .wait(guard)
94                .expect("poisoned scope completion lock");
95        }
96    }
97
98    fn record_panic(&self, err: Box<dyn Any + Send>) {
99        let mut panic_slot = self.panic.lock().expect("poisoned scope panic lock");
100        if panic_slot.is_none() {
101            *panic_slot = Some(err);
102        }
103    }
104
105    fn take_panic(&self) -> Option<Box<dyn Any + Send>> {
106        self.panic.lock().expect("poisoned scope panic lock").take()
107    }
108}
109
110pub struct ScopeRef<'env> {
111    shared: *const WorkerShared,
112    runtime: Arc<ScopeRuntime>,
113    _marker: PhantomData<&'env ()>,
114}
115
116impl<'env> ScopeRef<'env> {
117    fn shared(&self) -> &WorkerShared {
118        unsafe { &*self.shared }
119    }
120
121    fn runtime(&self) -> &ScopeRuntime {
122        &self.runtime
123    }
124}
125
126struct Task {
127    data: *mut (),
128    run_fn: unsafe fn(*mut ()),
129    drop_fn: unsafe fn(*mut ()),
130    runtime: Arc<ScopeRuntime>,
131}
132
133unsafe impl Send for Task {}
134
135impl Task {
136    fn new<W>(work: W, runtime: Arc<ScopeRuntime>) -> Self
137    where
138        W: FnOnce() + Send,
139    {
140        unsafe fn run_impl<W>(data: *mut ())
141        where
142            W: FnOnce() + Send,
143        {
144            let work = unsafe { Box::from_raw(data as *mut W) };
145            (*work)();
146        }
147
148        unsafe fn drop_impl<W>(data: *mut ())
149        where
150            W: FnOnce() + Send,
151        {
152            drop(unsafe { Box::from_raw(data as *mut W) });
153        }
154
155        let boxed = Box::new(work);
156        Self {
157            data: Box::into_raw(boxed) as *mut (),
158            run_fn: run_impl::<W>,
159            drop_fn: drop_impl::<W>,
160            runtime,
161        }
162    }
163
164    unsafe fn run(self) {
165        unsafe { (self.run_fn)(self.data) };
166    }
167
168    unsafe fn drop(self) {
169        unsafe { (self.drop_fn)(self.data) };
170    }
171}
172
173fn worker_loop(shared: Arc<WorkerShared>) {
174    loop {
175        let task = {
176            let mut state = shared.state.lock().expect("poisoned pool lock");
177            loop {
178                if state.shutdown {
179                    return;
180                }
181
182                if let Some(task) = state.queue.pop_front() {
183                    break task;
184                }
185
186                state = shared.cv.wait(state).expect("poisoned pool lock");
187            }
188        };
189
190        let runtime = Arc::clone(&task.runtime);
191        let result = catch_unwind(AssertUnwindSafe(|| unsafe { task.run() }));
192        if let Err(err) = result {
193            runtime.record_panic(err);
194        }
195        runtime.complete_task();
196    }
197}
198
199/// Native standard thread pool with persistent workers.
200///
201/// This is the default thread pool when the `std` feature is enabled without
202/// `transient-pool`, `persistent-pool-rayon`, or `wasm` features.
203/// Note that the thread pool to be used for a parallel computation can be set by the
204/// [`runner`] transformation separately for each parallel iterator.
205///
206/// Value of [`max_num_threads`] is determined as the minimum of:
207///
208/// * the available parallelism of the host obtained via `std::thread::available_parallelism()`, and
209/// * the upper bound set by the environment variable "ORX_NUM_THREADS", when set.
210///
211/// [`max_num_threads`]: ThreadPool::max_num_threads
212/// [`runner`]: crate::Par::runner
213#[derive(Clone)]
214pub struct BasicPool {
215    max_num_threads: NonZeroUsize,
216    inner: Arc<Inner>,
217}
218
219impl Default for BasicPool {
220    fn default() -> Self {
221        Self::new(NumThreads::Auto)
222    }
223}
224
225impl BasicPool {
226    /// Creates a `BasicPool` with persistent worker threads.
227    ///
228    /// The effective thread count is the minimum of the requested `num_threads`,
229    /// the `ORX_NUM_THREADS` environment limit when set, and the
230    /// available system parallelism.
231    pub fn new(num_threads: impl Into<NumThreads>) -> Self {
232        let num_threads = match num_threads.into() {
233            NumThreads::Auto => max_num_threads_by_env_and_resource(),
234            NumThreads::Max(n) => max_num_threads_by_env_and_resource().min(n),
235        };
236
237        let shared = Arc::new(WorkerShared {
238            state: Mutex::new(WorkerState {
239                shutdown: false,
240                queue: VecDeque::new(),
241            }),
242            cv: Condvar::new(),
243        });
244
245        let nt: usize = num_threads.into();
246        let mut workers = Vec::with_capacity(nt);
247        for _ in 0..nt {
248            let shared_cloned = Arc::clone(&shared);
249            workers.push(thread::spawn(move || worker_loop(shared_cloned)));
250        }
251
252        Self {
253            max_num_threads: num_threads,
254            inner: Arc::new(Inner {
255                shared,
256                workers: Mutex::new(workers),
257            }),
258        }
259    }
260
261    fn scope_impl<'env, 'scope, F>(&'env self, f: F)
262    where
263        'env: 'scope,
264        for<'s> F: FnOnce(&'s ScopeRef<'env>) + Send,
265    {
266        let runtime = Arc::new(ScopeRuntime::new());
267
268        let scope_ref = ScopeRef {
269            shared: Arc::as_ptr(&self.inner.shared),
270            runtime: Arc::clone(&runtime),
271            _marker: PhantomData,
272        };
273
274        let user_result = catch_unwind(AssertUnwindSafe(|| f(&scope_ref)));
275
276        runtime.wait_for_completion();
277
278        if let Err(err) = user_result {
279            resume_unwind(err);
280        }
281
282        if let Some(err) = runtime.take_panic() {
283            resume_unwind(err);
284        }
285    }
286}
287
288impl<'s, 'env, 'scope> Scope<'s, 'env, 'scope> for &'s ScopeRef<'env> {
289    fn run<W>(self, work: W)
290    where
291        'scope: 's,
292        'env: 'scope + 's,
293        W: FnOnce() + Send + 'scope + 'env,
294    {
295        self.runtime().begin_task();
296
297        let task = Task::new(work, Arc::clone(&self.runtime));
298        {
299            let mut state = self.shared().state.lock().expect("poisoned pool lock");
300            state.queue.push_back(task);
301        }
302        self.shared().cv.notify_one();
303    }
304}
305
306impl ThreadPool for BasicPool {
307    type ScopeRef<'s, 'env, 'scope>
308        = &'s ScopeRef<'env>
309    where
310        'scope: 's,
311        'env: 'scope + 's;
312
313    fn max_num_threads(&self) -> NonZeroUsize {
314        self.max_num_threads
315    }
316
317    fn scope<'env, 'scope, F>(&'env self, f: F)
318    where
319        'env: 'scope,
320        for<'s> F: FnOnce(&'s ScopeRef<'env>) + Send,
321    {
322        self.scope_impl(f)
323    }
324}
325
326impl ThreadPool for &BasicPool {
327    type ScopeRef<'s, 'env, 'scope>
328        = &'s ScopeRef<'env>
329    where
330        'scope: 's,
331        'env: 'scope + 's;
332
333    fn max_num_threads(&self) -> NonZeroUsize {
334        self.max_num_threads
335    }
336
337    fn scope<'env, 'scope, F>(&'env self, f: F)
338    where
339        'env: 'scope,
340        for<'s> F: FnOnce(&'s ScopeRef<'env>) + Send,
341    {
342        (*self).scope_impl(f)
343    }
344}
345
346impl ThreadPool for &mut BasicPool {
347    type ScopeRef<'s, 'env, 'scope>
348        = &'s ScopeRef<'env>
349    where
350        'scope: 's,
351        'env: 'scope + 's;
352
353    fn max_num_threads(&self) -> NonZeroUsize {
354        self.max_num_threads
355    }
356
357    fn scope<'env, 'scope, F>(&'env self, f: F)
358    where
359        'env: 'scope,
360        for<'s> F: FnOnce(&'s ScopeRef<'env>) + Send,
361    {
362        (*self).scope_impl(f)
363    }
364}