use crate::error::Error;
use core::fmt;
use core::num::NonZeroUsize;
const DEFAULT_STACK_BYTES: usize = 16 << 20;
pub struct Pool {
#[cfg(feature = "parallel")]
pub(crate) inner: rayon::ThreadPool,
#[cfg(feature = "parallel")]
_joiner: Joiner,
pub(crate) width: NonZeroUsize,
}
impl Pool {
pub fn builder() -> PoolBuilder {
PoolBuilder::new()
}
pub fn width(&self) -> NonZeroUsize {
self.width
}
}
#[cfg(feature = "parallel")]
struct Joiner {
handles: std::sync::Arc<std::sync::Mutex<Vec<std::thread::JoinHandle<()>>>>,
}
#[cfg(feature = "parallel")]
impl Drop for Joiner {
fn drop(&mut self) {
let handles = match self.handles.lock() {
Ok(mut guard) => std::mem::take(&mut *guard),
Err(poisoned) => std::mem::take(&mut *poisoned.into_inner()),
};
let me = std::thread::current().id();
if handles.iter().any(|h| h.thread().id() == me) {
return;
}
for handle in handles {
let _ = handle.join();
}
}
}
impl fmt::Debug for Pool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Pool").field("width", &self.width).finish()
}
}
#[cfg_attr(not(feature = "parallel"), allow(dead_code))]
pub struct PoolBuilder {
threads: Option<usize>,
stack_size: usize,
thread_name: Option<Box<dyn Fn(usize) -> String>>,
on_start: Option<Box<dyn Fn(usize) + Send + Sync>>,
}
impl PoolBuilder {
fn new() -> Self {
PoolBuilder {
threads: None,
stack_size: DEFAULT_STACK_BYTES,
thread_name: None,
on_start: None,
}
}
#[must_use]
pub fn threads(mut self, n: usize) -> Self {
self.threads = Some(n);
self
}
#[must_use]
pub fn thread_name(mut self, f: impl Fn(usize) -> String + 'static) -> Self {
self.thread_name = Some(Box::new(f));
self
}
#[must_use]
pub fn stack_size(mut self, bytes: usize) -> Self {
self.stack_size = bytes;
self
}
#[must_use]
pub fn on_start(mut self, f: impl Fn(usize) + Send + Sync + 'static) -> Self {
self.on_start = Some(Box::new(f));
self
}
pub fn build(self) -> Result<Pool, Error> {
if self.threads == Some(0) {
return Err(Error::ZeroThreads);
}
self.build_checked()
}
#[cfg(feature = "parallel")]
fn build_checked(self) -> Result<Pool, Error> {
let n = self
.threads
.unwrap_or_else(|| std::thread::available_parallelism().map_or(1, NonZeroUsize::get));
let handles = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let sink = std::sync::Arc::clone(&handles);
let mut builder = rayon::ThreadPoolBuilder::new()
.num_threads(n)
.stack_size(self.stack_size)
.spawn_handler(move |thread| {
let mut b = std::thread::Builder::new();
if let Some(name) = thread.name() {
b = b.name(name.to_owned());
}
if let Some(size) = thread.stack_size() {
b = b.stack_size(size);
}
let handle = b.spawn(move || thread.run())?;
match sink.lock() {
Ok(mut guard) => guard.push(handle),
Err(poisoned) => poisoned.into_inner().push(handle),
}
Ok(())
});
if let Some(name) = self.thread_name {
builder = builder.thread_name(name);
}
if let Some(hook) = self.on_start {
builder = builder.start_handler(hook);
}
let built = builder.build();
let inner = match built {
Ok(inner) => inner,
Err(e) => {
drop(Joiner { handles });
return Err(Error::PoolBuild(e.to_string()));
}
};
let width = NonZeroUsize::new(inner.current_num_threads()).unwrap_or(NonZeroUsize::MIN);
Ok(Pool {
inner,
_joiner: Joiner { handles },
width,
})
}
#[cfg(not(feature = "parallel"))]
fn build_checked(self) -> Result<Pool, Error> {
Ok(Pool {
width: NonZeroUsize::MIN,
})
}
}
impl fmt::Debug for PoolBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PoolBuilder")
.field("threads", &self.threads)
.field("stack_size", &self.stack_size)
.finish_non_exhaustive()
}
}
#[cfg(all(test, feature = "parallel"))]
mod tests {
use super::*;
use std::sync::{mpsc, Arc};
use std::time::Duration;
#[test]
fn drop_from_a_worker_of_the_pool_does_not_hang() {
let pool = Arc::new(Pool::builder().threads(2).build().unwrap());
let last = Arc::clone(&pool);
let (gate_tx, gate_rx) = mpsc::channel::<()>();
let (done_tx, done_rx) = mpsc::channel::<()>();
pool.inner.spawn(move || {
gate_rx.recv().unwrap();
drop(last); done_tx.send(()).unwrap();
});
drop(pool);
gate_tx.send(()).unwrap();
done_rx
.recv_timeout(Duration::from_secs(10))
.expect("drop on a worker hung");
}
}