use crate::awake::Awake;
use crate::cost::DispatchCosts;
use crate::error::Error;
use crate::policy::{Qos, WaitPolicy};
use core::fmt;
use core::num::NonZeroUsize;
use core::sync::atomic::Ordering;
use core::time::Duration;
use std::sync::Arc;
const DEFAULT_STACK_BYTES: usize = 16 << 20;
const DEFAULT_ACQUIRE_TIMEOUT: Duration = Duration::from_millis(2);
const PIN_PARTICIPANTS_DEFAULT: bool = false;
pub struct Pool {
#[cfg(feature = "parallel")]
pub(crate) inner: rayon::ThreadPool,
#[cfg(feature = "parallel")]
_joiner: Joiner,
pub(crate) width: NonZeroUsize,
pub(crate) awake: Arc<Awake>,
}
impl Pool {
pub fn builder() -> PoolBuilder {
PoolBuilder::new()
}
pub fn width(&self) -> NonZeroUsize {
self.width
}
pub fn wait_policy(&self) -> WaitPolicy {
self.awake.defaults
}
pub fn release_idle(&self) {
self.awake.release_epoch.fetch_add(1, Ordering::AcqRel);
}
pub fn dispatch_costs(&self) -> DispatchCosts {
self.awake.costs
}
pub fn pin_participants(&self) -> bool {
self.awake.pin
}
pub fn resident_members(&self) -> usize {
self.awake.resident()
}
}
impl Drop for Pool {
fn drop(&mut self) {
self.awake.shutdown.store(true, Ordering::Release);
}
}
#[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>>,
wait: WaitPolicy,
acquire_timeout: Duration,
qos: Option<Qos>,
costs: DispatchCosts,
pin_participants: bool,
}
impl PoolBuilder {
fn new() -> Self {
PoolBuilder {
threads: None,
stack_size: DEFAULT_STACK_BYTES,
thread_name: None,
on_start: None,
wait: WaitPolicy::DEFAULT,
acquire_timeout: DEFAULT_ACQUIRE_TIMEOUT,
qos: None,
costs: DispatchCosts::host_default(),
pin_participants: PIN_PARTICIPANTS_DEFAULT,
}
}
#[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
}
#[must_use]
pub fn idle_spin(mut self, spin: Duration) -> Self {
self.wait = WaitPolicy::new(spin, self.wait.idle_runs_rayon());
self
}
#[must_use]
pub fn idle_runs_rayon(mut self, on: bool) -> Self {
self.wait = WaitPolicy::new(self.wait.idle_spin(), on);
self
}
#[must_use]
pub fn acquire_timeout(mut self, timeout: Duration) -> Self {
self.acquire_timeout = timeout;
self
}
#[must_use]
pub fn qos(mut self, qos: Qos) -> Self {
self.qos = Some(qos);
self
}
#[must_use]
pub fn dispatch_costs(mut self, costs: DispatchCosts) -> Self {
self.costs = costs;
self
}
#[must_use]
pub fn pin_participants(mut self, on: bool) -> Self {
self.pin_participants = on;
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 qos = self.qos.or(Qos::PLATFORM_DEFAULT);
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 || {
if let Some(q) = qos {
q.apply_to_current_thread();
}
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,
awake: Arc::new(Awake::new(
width.get(),
self.wait,
self.acquire_timeout,
self.costs,
self.pin_participants,
)),
})
}
#[cfg(not(feature = "parallel"))]
fn build_checked(self) -> Result<Pool, Error> {
Ok(Pool {
width: NonZeroUsize::MIN,
awake: Arc::new(Awake::new(
1,
self.wait,
self.acquire_timeout,
self.costs,
self.pin_participants,
)),
})
}
}
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)
.field("wait", &self.wait)
.field("acquire_timeout", &self.acquire_timeout)
.field("pin_participants", &self.pin_participants)
.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");
}
}