use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::time::Instant;
use std::{collections::VecDeque, thread, time::Duration};
use log::{debug, error};
use super::worker::Worker;
use crate::config::thread_pool_config::ThreadPoolConfig;
pub(crate) type Task = Box<dyn FnOnce() + Send + 'static>;
pub struct ThreadPool {
workers: Arc<Mutex<Vec<Worker>>>,
config: ThreadPoolConfig,
task_queue: Arc<Mutex<VecDeque<Task>>>,
condvar: Arc<Condvar>,
rejected_execution_handler: Box<dyn Fn(Task, &mut VecDeque<Task>) + Send + Sync>,
current_threads: Arc<AtomicUsize>,
}
impl ThreadPool {
pub fn new(
config: ThreadPoolConfig,
thread_name: Option<String>,
rejected_execution_handler: Box<dyn Fn(Task, &mut VecDeque<Task>) + Send + Sync>,
) -> ThreadPool {
let task_queue = Arc::new(Mutex::new(VecDeque::with_capacity(config.get_queue_size())));
let condvar = Arc::new(Condvar::new());
let workers = Arc::new(Mutex::new(Vec::new()));
let current_threads = Arc::new(AtomicUsize::new(0));
let pool = ThreadPool {
workers: Arc::clone(&workers),
config: config.clone(),
task_queue: Arc::clone(&task_queue),
condvar: Arc::clone(&condvar),
rejected_execution_handler,
current_threads: Arc::clone(¤t_threads),
};
for id in 0..config.get_core_threads() {
pool.spawn_worker(id, thread_name.clone(), true);
}
pool
}
fn spawn_worker(&self, id: usize, thread_name: Option<String>, is_core: bool) {
let task_queue = Arc::clone(&self.task_queue);
let condvar = Arc::clone(&self.condvar);
let keep_alive = if is_core {
None } else {
self.config.get_keep_alive()
};
let current_threads = Arc::clone(&self.current_threads);
let body = Box::new(move || {
debug!("Worker {} started", id);
let mut last_active = Instant::now();
loop {
let task = {
let mut task_queue = task_queue.lock().unwrap();
loop {
if let Some(task) = task_queue.pop_front() {
break Some(task); }
let (new_queue, timeout_result) = condvar
.wait_timeout(task_queue, Duration::from_millis(1000))
.unwrap();
task_queue = new_queue;
if timeout_result.timed_out() && task_queue.is_empty() {
break None; }
}
};
if let Some(task) = task {
debug!("Worker {} executing task", id);
let start_time = Instant::now();
task(); debug!("Worker {} finished task in {:?}", id, start_time.elapsed());
last_active = Instant::now();
}
if let Some(keep_alive) = keep_alive {
if last_active.elapsed() > keep_alive {
debug!("Worker {} exiting due to inactivity", id);
current_threads.fetch_sub(1, Ordering::Relaxed);
break;
}
}
}
});
let handle = thread::Builder::new()
.name(if let Some(name) = thread_name {
format!("{}-worker-{}", name, id)
} else {
format!("next-web-task-worker-{}", id)
})
.spawn(body)
.unwrap();
if let Ok(mut workers) = self.workers.lock() {
workers.push(Worker::new(id, Some(handle), is_core));
self.current_threads.fetch_add(1, Ordering::Relaxed);
} else {
error!("Worker {} failed to spawn", id);
}
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let task = Box::new(f);
let mut queue = match self.task_queue.lock() {
Ok(q) => q,
Err(_) => {
error!("Task queue mutex poisoned");
return;
}
};
if queue.len() < self.config.get_queue_size() {
queue.push_back(task);
self.condvar.notify_one();
} else {
let current = self.current_threads.load(Ordering::Relaxed);
if current < self.config.get_max_threads() {
debug!("Spawning temporary worker");
self.spawn_worker(current + 1, Some("temporary-worker".into()), false);
queue.push_back(task);
self.condvar.notify_one();
} else {
(self.rejected_execution_handler)(task, &mut queue);
}
}
}
}
#[derive(Clone)]
pub enum RejectedExecutionHandler {
Abort,
CallerRuns,
DiscardOldest,
}
impl RejectedExecutionHandler {
pub fn to_handler(&self) -> Box<dyn Fn(Task, &mut VecDeque<Task>) + Send + Sync> {
match self {
Self::Abort => Box::new(|_task, _| error!("Task rejected!! no data")),
Self::CallerRuns => Box::new(|task, _| task()),
Self::DiscardOldest => Box::new(|task, queue| {
queue.pop_front();
queue.push_back(task);
}),
}
}
}