use std::sync::atomic::{AtomicPtr, Ordering};
struct Entry {
pid: u32,
pool: rayon::ThreadPool,
}
static CURRENT: AtomicPtr<Entry> = AtomicPtr::new(std::ptr::null_mut());
pub fn pool() -> Option<&'static rayon::ThreadPool> {
let pid = std::process::id();
loop {
let seen = CURRENT.load(Ordering::Acquire);
if let Some(entry) = unsafe { seen.as_ref() } {
if entry.pid == pid {
return Some(&entry.pool);
}
}
let pool = rayon::ThreadPoolBuilder::new().build().ok()?;
let fresh = Box::into_raw(Box::new(Entry { pid, pool }));
match CURRENT.compare_exchange(seen, fresh, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => return Some(unsafe { &(*fresh).pool }),
Err(_) => drop(unsafe { Box::from_raw(fresh) }),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rayon::prelude::*;
#[test]
fn same_process_reuses_one_pool() {
let a = pool().expect("pool") as *const _;
let b = pool().expect("pool") as *const _;
assert_eq!(a, b);
}
#[test]
fn a_forked_child_runs_parallel_work_on_its_own_pool() {
let parent = pool().expect("pool");
assert_eq!(parent.install(|| (0..64).into_par_iter().sum::<i32>()), 2016);
match unsafe { libc::fork() } {
-1 => panic!("fork: {}", std::io::Error::last_os_error()),
0 => {
let sum = pool().map(|p| p.install(|| (0..64).into_par_iter().sum::<i32>()));
unsafe { libc::_exit(if sum == Some(2016) { 0 } else { 1 }) }
}
child => {
let mut status = 0;
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(20);
loop {
let done = unsafe { libc::waitpid(child, &mut status, libc::WNOHANG) };
if done == child {
break;
}
if std::time::Instant::now() > deadline {
unsafe { libc::kill(child, libc::SIGKILL) };
unsafe { libc::waitpid(child, &mut status, 0) };
panic!("forked child hung running parallel work");
}
std::thread::sleep(std::time::Duration::from_millis(20));
}
assert!(libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0);
}
}
}
}