pub mod affinity;
mod builder;
mod cpu;
mod data;
mod error;
mod global;
mod local;
mod queue;
mod sentry;
pub use crate::builder::Builder;
pub use crate::error::Error;
pub use crate::local::SpawnFuture;
use crate::data::Data;
use crate::global::THREADPOOL;
use crate::queue::Queue;
use crate::sentry::Sentry;
use async_task::{Builder as TaskBuilder, Runnable};
use parking_lot::Mutex;
use std::future::Future;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
pub const MAX_THREADS: usize = 512;
pub async fn spawn<F, R>(func: F) -> R
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
if let Some(threadpool) = THREADPOOL.get() {
threadpool.spawn(func).await
} else {
func()
}
}
pub async fn spawn_local<'pool, F, R>(func: F) -> R
where
F: FnOnce() -> R,
F: Send + 'pool,
R: Send + 'pool,
{
if let Some(threadpool) = THREADPOOL.get() {
threadpool.spawn_local(func).await
} else {
func()
}
}
pub struct Threadpool {
pub(crate) data: Arc<Data>,
}
impl Default for Threadpool {
fn default() -> Self {
Threadpool::new(num_cpus::get())
}
}
impl Threadpool {
pub fn new(workers: usize) -> Self {
let workers = workers.clamp(1, MAX_THREADS);
let data = Arc::new(Data {
name: None,
stack_size: None,
num_threads: AtomicUsize::new(workers),
thread_count: AtomicUsize::new(0),
queue: Arc::new(Queue::new(workers)),
thread_handles: Mutex::new(Vec::new()),
});
for index in 0..workers {
Self::spin_up(None, data.clone(), index);
}
Threadpool {
data,
}
}
pub fn spawn<F, R>(&self, func: F) -> impl Future<Output = R> + Send + 'static
where
F: FnOnce() -> R,
F: Send + 'static,
R: Send + 'static,
{
let queue = self.data.queue.clone();
let schedule = move |runnable: Runnable| queue.push(runnable);
let (runnable, task) = TaskBuilder::new().spawn(
move |()| async move { std::panic::catch_unwind(std::panic::AssertUnwindSafe(func)) },
schedule,
);
runnable.schedule();
async move {
match task.await {
Ok(value) => value,
Err(payload) => std::panic::resume_unwind(payload),
}
}
}
pub fn spawn_local<'pool, F, R>(&'pool self, func: F) -> SpawnFuture<'pool, R>
where
F: FnOnce() -> R,
F: Send + 'pool,
R: Send + 'pool,
{
let queue = self.data.queue.clone();
let schedule = move |runnable: Runnable| queue.push(runnable);
let (runnable, task) = unsafe {
TaskBuilder::new().spawn_unchecked(
move |()| async move { std::panic::catch_unwind(std::panic::AssertUnwindSafe(func)) },
schedule,
)
};
SpawnFuture::new(runnable, task)
}
pub fn build_global(self) -> Result<(), Error> {
if THREADPOOL.get().is_some() {
return Err(Error::GlobalThreadpoolExists);
}
THREADPOOL.get_or_init(|| self);
Ok(())
}
pub fn thread_count(&self) -> usize {
self.data.thread_count.load(Ordering::Relaxed)
}
pub fn num_threads(&self) -> usize {
self.data.num_threads.load(Ordering::Relaxed)
}
#[cfg(not(target_family = "wasm"))]
pub(crate) fn spin_up(coreid: Option<usize>, data: Arc<Data>, index: usize) {
let mut builder = std::thread::Builder::new();
if let Some(ref name) = data.name {
builder = builder.name(name.clone());
}
if let Some(stack_size) = data.stack_size {
builder = builder.stack_size(stack_size);
}
data.thread_count.fetch_add(1, Ordering::Relaxed);
let sentry = Sentry::new(coreid, index, Arc::downgrade(&data));
let queue = data.queue.clone();
let data_clone = data.clone();
let handle = builder.spawn(move || {
if let Some(coreid) = coreid {
affinity::set_for_current(coreid.into());
}
while let Some(runnable) = queue.pop_blocking(index) {
runnable.run();
}
sentry.cancel();
});
if let Ok(handle) = handle {
data_clone.thread_handles.lock().push(handle);
}
}
#[cfg(target_family = "wasm")]
pub(crate) fn spin_up(_coreid: Option<usize>, _data: Arc<Data>, _index: usize) {
}
}
impl Drop for Threadpool {
fn drop(&mut self) {
self.data.queue.shutdown();
let handles = self.data.thread_handles.lock().drain(..).collect::<Vec<_>>();
for handle in handles {
let _ = handle.join();
}
self.data.thread_count.store(0, Ordering::Relaxed);
}
}