use std::collections::VecDeque;
use std::sync::{Condvar, Mutex, MutexGuard};
use std::thread::JoinHandle;
use std::time::Duration;
pub(super) type Job = Box<dyn FnOnce() + Send>;
const KEEP_ALIVE: Duration = Duration::from_secs(10);
const MAX_WORKERS: usize = 512;
struct State {
queue: VecDeque<Job>,
idle: usize,
workers: Vec<JoinHandle<()>>,
shutdown: bool,
}
struct Pool {
state: Mutex<State>,
work_ready: Condvar,
}
static POOL: Pool = Pool {
state: Mutex::new(State {
queue: VecDeque::new(),
idle: 0,
workers: Vec::new(),
shutdown: false,
}),
work_ready: Condvar::new(),
};
fn lock() -> MutexGuard<'static, State> {
POOL.state.lock().unwrap_or_else(|e| e.into_inner())
}
pub(super) fn submit(job: Job) {
let mut state = lock();
state.workers.retain(|handle| !handle.is_finished());
state.queue.push_back(job);
if state.idle == 0 && state.workers.len() < MAX_WORKERS {
match std::thread::Builder::new()
.name("telar-task".to_string())
.spawn(worker)
{
Ok(handle) => state.workers.push(handle),
Err(e) => assert!(
!state.workers.is_empty(),
"cannot spawn a telar task thread: {e}"
),
}
}
drop(state);
POOL.work_ready.notify_one();
}
fn worker() {
let mut state = lock();
loop {
if let Some(job) = state.queue.pop_front() {
drop(state);
job();
state = lock();
continue;
}
if state.shutdown {
return;
}
state.idle += 1;
let (resumed, wait) = POOL
.work_ready
.wait_timeout(state, KEEP_ALIVE)
.unwrap_or_else(|e| e.into_inner());
state = resumed;
state.idle -= 1;
if wait.timed_out() && state.queue.is_empty() {
return;
}
}
}
pub(super) fn shutdown_and_join() {
let (queued, handles) = {
let mut state = lock();
state.shutdown = true;
(
std::mem::take(&mut state.queue),
std::mem::take(&mut state.workers),
)
};
drop(queued);
POOL.work_ready.notify_all();
for handle in handles {
let _ = handle.join();
}
lock().shutdown = false;
}