#![forbid(unsafe_code)]
pub(crate) mod builder;
pub(crate) mod error;
pub(crate) mod pool;
pub(crate) mod pool_inner;
use crate::{error::ThreadPoolError, pool::ThreadPool};
use core::fmt;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use core::time::Duration;
use derive_more::Display;
use futures_channel::oneshot;
use parking_lot::Mutex;
const ENV_MAX_THREADS: &str = "ACTIX_THREADPOOL";
const ENV_MIN_THREADS: &str = "ACTIX_THREADPOOL_MIN";
const ENV_IDLE_TIMEOUT: &str = "ACTIX_THREADPOOL_TIMEOUT";
lazy_static::lazy_static! {
pub(crate) static ref POOL: Mutex<ThreadPool> = {
let max = parse_env(ENV_MAX_THREADS).unwrap_or_else(|| num_cpus::get() * 5);
let min = parse_env(ENV_MIN_THREADS).unwrap_or(1);
let dur = parse_env(ENV_IDLE_TIMEOUT).unwrap_or(30);
Mutex::new(ThreadPool::builder()
.thread_name("actix-dynamic-threadpool")
.max_threads(max)
.min_threads(min)
.idle_timeout(Duration::from_secs(dur))
.build())
};
}
thread_local! {
static POOL_LOCAL: ThreadPool = {
POOL.lock().clone()
}
}
fn parse_env<R: std::str::FromStr>(env: &str) -> Option<R> {
std::env::var(env).ok().and_then(|val| {
val.parse()
.map_err(|_| log::warn!("Can not parse {} value, using default", env))
.ok()
})
}
#[derive(Debug, Display)]
pub enum BlockingError<E: fmt::Debug> {
#[display(fmt = "{:?}", _0)]
Error(E),
#[display(fmt = "Thread pool is gone")]
Canceled,
}
impl<E: fmt::Debug> std::error::Error for BlockingError<E> {}
pub fn run<F, I, E>(f: F) -> CpuFuture<I, E>
where
F: FnOnce() -> Result<I, E> + Send + 'static,
I: Send + 'static,
E: Send + fmt::Debug + 'static,
{
let (tx, rx) = oneshot::channel();
POOL_LOCAL.with(|pool| {
let _ = pool.execute(move || {
if !tx.is_canceled() {
let _ = tx.send(f());
}
});
});
CpuFuture { rx }
}
pub struct CpuFuture<I, E> {
rx: oneshot::Receiver<Result<I, E>>,
}
impl<I, E: fmt::Debug> Future for CpuFuture<I, E> {
type Output = Result<I, BlockingError<E>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let rx = Pin::new(&mut self.rx);
let res = match rx.poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(res) => res
.map_err(|_| BlockingError::Canceled)
.and_then(|res| res.map_err(BlockingError::Error)),
};
Poll::Ready(res)
}
}
#[cfg(test)]
mod test {
use super::*;
use core::time::Duration;
use std::thread;
#[test]
fn init() {
let pool = ThreadPool::builder()
.max_threads(12)
.min_threads(7)
.thread_name("test-pool")
.idle_timeout(Duration::from_secs(5))
.build();
let state = pool.state();
assert_eq!(state.active_threads, 7);
assert_eq!(state.max_threads, 12);
assert_eq!(state.name, Some("test-pool-worker"));
}
#[test]
fn recycle() {
let pool = ThreadPool::builder()
.max_threads(12)
.min_threads(3)
.thread_name("test-pool")
.idle_timeout(Duration::from_millis(300))
.build();
(0..1024).for_each(|_| {
let _ = pool.execute(|| {
thread::sleep(Duration::from_nanos(1));
});
});
let state = pool.state();
assert_eq!(12, state.active_threads);
thread::sleep(Duration::from_secs(4));
let state = pool.state();
assert_eq!(3, state.active_threads);
}
#[test]
fn panic_recover() {
let pool = ThreadPool::builder()
.max_threads(12)
.min_threads(3)
.thread_name("test-pool")
.build();
let _ = pool.execute(|| {
panic!("This is a on purpose panic for testing panic recovery");
});
thread::sleep(Duration::from_millis(100));
let state = pool.state();
assert_eq!(3, state.active_threads);
(0..128).for_each(|_| {
let _ = pool.execute(|| {
thread::sleep(Duration::from_millis(1));
});
});
let _ = pool.execute(|| {
panic!("This is a on purpose panic for testing panic recovery");
});
thread::sleep(Duration::from_millis(100));
let state = pool.state();
assert_eq!(11, state.active_threads);
}
#[test]
fn no_eager_spawn() {
let pool = ThreadPool::builder()
.max_threads(12)
.thread_name("test-pool")
.build();
let _a = pool.execute(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(100));
let _a = pool.execute(|| {
thread::sleep(Duration::from_millis(1));
});
thread::sleep(Duration::from_millis(100));
let state = pool.state();
assert_eq!(1, state.active_threads);
}
}