use std::collections::VecDeque;
use std::sync::{Arc, Condvar, Mutex, OnceLock, PoisonError};
type Job = Box<dyn FnOnce() + Send + 'static>;
#[derive(Default)]
struct Pool {
state: Mutex<State>,
ready: Condvar,
}
#[derive(Default)]
struct State {
jobs: VecDeque<Job>,
idle: usize,
}
fn pool() -> &'static Arc<Pool> {
static POOL: OnceLock<Arc<Pool>> = OnceLock::new();
POOL.get_or_init(Arc::default)
}
impl Pool {
fn submit(self: &Arc<Self>, job: Job) {
let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
state.jobs.push_back(job);
if state.jobs.len() > state.idle {
let pool = self.clone();
std::thread::Builder::new()
.name("nuvec-ring".into())
.spawn(move || pool.work())
.expect("spawn a ring thread");
}
self.ready.notify_one();
}
fn work(&self) {
loop {
let job = {
let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
loop {
if let Some(job) = state.jobs.pop_front() {
break job;
}
state.idle += 1;
state = self
.ready
.wait(state)
.unwrap_or_else(PoisonError::into_inner);
state.idle -= 1;
}
};
job();
}
}
}
#[allow(unsafe_code)]
pub(crate) fn join<'a, A: Send + 'a, B>(
side: impl FnOnce() -> A + Send + 'a,
main: impl FnOnce() -> B,
) -> (A, B) {
type Slot<A> = (Mutex<Option<std::thread::Result<A>>>, Condvar);
let slot: Arc<Slot<A>> = Arc::new((Mutex::new(None), Condvar::new()));
let job: Box<dyn FnOnce() + Send + 'a> = Box::new({
let slot = slot.clone();
move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(side));
*slot.0.lock().unwrap_or_else(PoisonError::into_inner) = Some(result);
slot.1.notify_one();
}
});
let job: Job = unsafe { std::mem::transmute::<Box<dyn FnOnce() + Send + 'a>, Job>(job) };
pool().submit(job);
let main = std::panic::catch_unwind(std::panic::AssertUnwindSafe(main));
let mut done = slot.0.lock().unwrap_or_else(PoisonError::into_inner);
let side = loop {
if let Some(result) = done.take() {
break result;
}
done = slot.1.wait(done).unwrap_or_else(PoisonError::into_inner);
};
drop(done);
match (side, main) {
(Ok(a), Ok(b)) => (a, b),
(Err(p), _) | (_, Err(p)) => std::panic::resume_unwind(p),
}
}