use std::{
collections::VecDeque,
num::NonZeroUsize,
ops::Deref,
panic::{self, AssertUnwindSafe},
sync::{Condvar, Mutex, MutexGuard, PoisonError},
thread,
time::Duration,
};
use super::dispose;
pub(crate) struct Pool {
queue: Mutex<Queue>,
job_queued: Condvar,
name: &'static str,
cap: NonZeroUsize,
idle_timeout: Duration,
}
pub(crate) type Job = Box<dyn FnOnce() + Send>;
impl Pool {
pub(crate) const fn new(name: &'static str, cap: NonZeroUsize, idle_timeout: Duration) -> Self {
Self {
queue: Mutex::new(Queue {
jobs: VecDeque::new(),
threads: 0,
idle: 0,
}),
job_queued: Condvar::new(),
name,
cap,
idle_timeout,
}
}
pub(crate) fn submit<P>(pool: P, job: Job)
where
P: Deref<Target = Self> + Clone + Send + 'static,
{
let mut queue = pool.lock();
queue.jobs.push_back(job);
pool.job_queued.notify_one();
while queue.jobs.len() > queue.idle && queue.threads < pool.cap.get() {
let handle = pool.clone();
let spawned = thread::Builder::new()
.name(pool.name.into())
.spawn(move || serve(handle));
match spawned {
Ok(_) => {
queue.threads += 1;
queue.idle += 1;
}
Err(_) if queue.threads > 0 => break,
Err(error) => {
let job = queue.jobs.pop_back();
drop(queue);
drop(job);
panic!("failed to spawn a thread for blocking work: {error}");
}
}
}
}
#[cfg(test)]
pub(crate) fn threads(&self) -> usize {
self.lock().threads
}
#[cfg(test)]
pub(crate) fn idle_threads(&self) -> usize {
self.lock().idle
}
fn lock(&self) -> MutexGuard<'_, Queue> {
self.queue.lock().unwrap_or_else(PoisonError::into_inner)
}
}
fn serve<P>(pool: P)
where
P: Deref<Target = Pool>,
{
let mut queue = pool.lock();
queue.idle -= 1;
loop {
if let Some(job) = queue.jobs.pop_front() {
drop(queue);
if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(job)) {
dispose(payload);
}
queue = pool.lock();
continue;
}
queue.idle += 1;
let (guard, wait) = pool
.job_queued
.wait_timeout(queue, pool.idle_timeout)
.unwrap_or_else(PoisonError::into_inner);
queue = guard;
queue.idle -= 1;
if wait.timed_out() && queue.jobs.is_empty() {
queue.threads -= 1;
return;
}
}
}
struct Queue {
jobs: VecDeque<Job>,
threads: usize,
idle: usize,
}