#![cfg(feature = "multi")]
#![allow(clippy::new_ret_no_self)]
#![allow(clippy::type_complexity)]
use std::{
any::Any,
collections::HashMap,
env,
marker::PhantomData,
panic,
sync::{
self,
atomic::{self, AtomicBool, AtomicUsize, Ordering},
Arc,
},
thread::JoinHandle,
time::{self},
};
use crate::valtron::{Ctx, Stream, DEFAULT_YIELD_WAIT_TIME};
use concurrent_queue::{ConcurrentQueue, PushError};
use derive_more::derive::From;
use foundation_compact::rng::ChaCha8Rng;
use foundation_compact::rng::Rng;
use foundation_compact::rng::SeedableRng;
use std::str::FromStr;
use crate::{
extensions::result_ext::SendableBoxedError,
retries::ExponentialBackoffDecider,
synca::{
mpp::{self},
EntryList, IdleMan, LockSignal, OnSignal, SleepyMan, WaitGroup,
},
valtron::{
AnyResult, LocalThreadExecutor, NotifyQueue, NotifyQueueStreamIterator, NotifyRecvIterator,
},
};
use crate::valtron::{
BoxedExecutionEngine, BoxedPanicHandler, BoxedSendExecutionIterator, ExecutorError,
FnMutReady, FnReady, OnNext, PriorityOrder, ProcessController, ReadyConsumingIter,
SharedTaskQueue, TaskIterator, TaskReadyResolver, TaskStatus,
};
use crate::valtron::{ThreadActivity, ThreadId};
use crate::valtron::{
executors::constants::{
BACK_OFF_JITER, BACK_OFF_MAX_DURATION, BACK_OFF_MIN_DURATION, BACK_OFF_THREAD_FACTOR,
DEFAULT_OP_READ_TIME, MAX_ROUNDS_IDLE_COUNT, MAX_ROUNDS_WHEN_SLEEPING_ENDS,
},
ConsumingIter, DoNext, StreamConsumingIter,
};
use crate::compati::{CondVarMutex, Condvar, Mutex, RwLock};
use foundation_nostd::comp::condvar_comp::{CondVar, CondVarMutex as CvMutex};
use crate::valtron::{
split_thread_count, BackgroundJobConfig, BackgroundJobRegistry, GenericResult, ShutdownOutcome,
};
static REGISTRY: Mutex<Option<Arc<ThreadRegistry>>> = Mutex::new(None);
static BG_REGISTRY: Mutex<Option<Arc<BackgroundJobRegistry>>> = Mutex::new(None);
static POOL_LIFECYCLE_GATE: FairGate = FairGate::new();
pub struct FairGate {
next_ticket: AtomicUsize,
now_serving: AtomicUsize,
inner: CondVarMutex<()>,
cond: Condvar,
}
impl FairGate {
const fn new() -> Self {
Self {
next_ticket: AtomicUsize::new(0),
now_serving: AtomicUsize::new(0),
inner: CondVarMutex::new(()),
cond: Condvar::new(),
}
}
fn acquire(&'static self) -> FairGateGuard {
let my_ticket = self.next_ticket.fetch_add(1, Ordering::SeqCst);
let mut guard = self.inner.lock().unwrap_or_else(|p| p.into_inner());
while self.now_serving.load(Ordering::SeqCst) != my_ticket {
guard = self.cond.wait(guard).unwrap_or_else(|p| p.into_inner());
}
FairGateGuard { gate: self }
}
}
pub struct FairGateGuard {
gate: &'static FairGate,
}
impl Drop for FairGateGuard {
fn drop(&mut self) {
let _guard = self.gate.inner.lock().unwrap_or_else(|p| p.into_inner());
self.gate.now_serving.fetch_add(1, Ordering::SeqCst);
self.gate.cond.notify_all();
}
}
pub(crate) fn clear_global_registries() {
let mut reg = REGISTRY.lock().unwrap_or_else(|p| p.into_inner());
*reg = None;
drop(reg);
let mut bg = BG_REGISTRY.lock().unwrap_or_else(|p| p.into_inner());
*bg = None;
}
#[derive(Clone)]
pub struct LocalPoolHandle {
shared_tasks: SharedTaskQueue,
latch: Arc<LockSignal>,
kill_signal: Arc<OnSignal>,
yielders: SharedThreadYielders,
}
impl LocalPoolHandle {
fn new(registry: &Arc<ThreadRegistry>) -> Self {
Self {
shared_tasks: registry.shared_tasks(),
latch: registry.latch(),
kill_signal: registry.kill_signal(),
yielders: registry.yielders(),
}
}
#[must_use]
pub fn spawn<Task>(
&self,
) -> ThreadPoolTaskBuilder<
Task::Ready,
Task::Pending,
Box<dyn TaskReadyResolver<Task::Ready, Task::Pending> + Send + 'static>,
Task,
>
where
Task::Ready: Send + 'static,
Task::Pending: Send + 'static,
Task: TaskIterator + Send + 'static,
{
ThreadPoolTaskBuilder::new(self.shared_tasks.clone(), self.latch.clone())
.with_yielders(self.yielders.clone())
}
#[must_use]
pub fn spawn2<Task, Resolver>(
&self,
) -> ThreadPoolTaskBuilder<Task::Ready, Task::Pending, Resolver, Task>
where
Task::Ready: Send + 'static,
Task::Pending: Send + 'static,
Task: TaskIterator + Send + 'static,
Resolver: TaskReadyResolver<Task::Ready, Task::Pending> + Send + 'static,
{
ThreadPoolTaskBuilder::new(self.shared_tasks.clone(), self.latch.clone())
.with_yielders(self.yielders.clone())
}
pub fn kill(&self) {
self.kill_signal.turn_on();
self.latch.signal_all();
}
}
pub fn get_pool() -> LocalPoolHandle {
let registry = REGISTRY
.lock()
.unwrap_or_else(|p| p.into_inner())
.clone()
.expect("Thread pool not initialized, ensure to call block_on or initialize_pool first");
LocalPoolHandle::new(®istry)
}
#[must_use]
pub fn fork_ctx() -> Ctx {
let registry = REGISTRY
.lock()
.unwrap_or_else(|p| p.into_inner())
.clone()
.expect("Thread pool not initialized, ensure to call block_on or initialize_pool first");
registry.fork_ctx()
}
pub fn block_on<F>(seed_from_rng: u64, thread_num: Option<usize>, setup: F) -> PoolGuard
where
F: FnOnce(LocalPoolHandle),
{
let guard = initialize_pool(seed_from_rng, thread_num);
let handle = get_pool();
setup(handle);
guard.waitgroup().wait();
guard
}
pub fn initialize_pool(seed_for_rng: u64, user_thread_num: Option<usize>) -> PoolGuard {
initialize_pool_with_config(seed_for_rng, user_thread_num, PoolConfig::default())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PoolConfig {
pub background_shutdown_timeout: std::time::Duration,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
background_shutdown_timeout: BackgroundJobConfig::DEFAULT_SHUTDOWN_TIMEOUT,
}
}
}
impl PoolConfig {
#[must_use]
pub fn with_background_shutdown_timeout(mut self, timeout: std::time::Duration) -> Self {
self.background_shutdown_timeout = timeout;
self
}
}
pub fn initialize_pool_with_config(
seed_for_rng: u64,
user_thread_num: Option<usize>,
config: PoolConfig,
) -> PoolGuard {
let lifecycle = POOL_LIFECYCLE_GATE.acquire();
clear_global_registries();
let thread_num = match user_thread_num {
None => {
let count = get_allocatable_thread_count();
count.max(3)
}
Some(num) => num.max(3),
};
let (task_threads, bg_threads) = split_thread_count(thread_num);
let registry = Arc::new(ThreadRegistry::with_seed_and_threads(
seed_for_rng,
task_threads,
));
for _ in 0..task_threads {
registry
.spawn_worker()
.expect("Failed to spawn worker thread");
}
let bg_registry = BackgroundJobRegistry::with_config(
BackgroundJobConfig::new(bg_threads)
.with_shutdown_timeout(config.background_shutdown_timeout),
);
let mut reg_guard = REGISTRY.lock().unwrap_or_else(|p| p.into_inner());
*reg_guard = Some(registry.clone());
let mut bg_guard = BG_REGISTRY.lock().unwrap_or_else(|p| p.into_inner());
*bg_guard = Some(bg_registry.clone());
drop(reg_guard);
drop(bg_guard);
PoolGuard::with_bg_registry(registry, bg_registry, lifecycle)
}
pub fn run_background_job(job: impl FnOnce() + Send + 'static) -> GenericResult<()> {
let bg_registry = BG_REGISTRY
.lock()
.unwrap_or_else(|p| p.into_inner())
.clone()
.expect("Background job pool not initialized, ensure to call initialize_pool first");
bg_registry.submit(job)
}
#[must_use]
pub fn spawn<Task>() -> ThreadPoolTaskBuilder<
Task::Ready,
Task::Pending,
Box<dyn TaskReadyResolver<Task::Ready, Task::Pending> + Send + 'static>,
Task,
>
where
Task::Ready: Send + 'static,
Task::Pending: Send + 'static,
Task: TaskIterator + Send + 'static,
{
get_pool().spawn::<Task>()
}
#[must_use]
pub fn spawn2<Task, Resolver>(
) -> ThreadPoolTaskBuilder<Task::Ready, Task::Pending, Resolver, Task>
where
Task::Ready: Send + 'static,
Task::Pending: Send + 'static,
Task: TaskIterator + Send + 'static,
Resolver: TaskReadyResolver<Task::Ready, Task::Pending> + Send + 'static,
{
get_pool().spawn2::<Task, Resolver>()
}
pub struct PoolGuard {
registry: Arc<ThreadRegistry>,
bg_registry: Option<Arc<crate::valtron::BackgroundJobRegistry>>,
shut_down: AtomicBool,
_lifecycle: Option<FairGateGuard>,
}
impl PoolGuard {
#[must_use]
pub fn new(registry: Arc<ThreadRegistry>) -> Self {
Self {
registry,
bg_registry: None,
shut_down: AtomicBool::new(false),
_lifecycle: None,
}
}
#[must_use]
pub fn with_bg_registry(
registry: Arc<ThreadRegistry>,
bg_registry: Arc<crate::valtron::BackgroundJobRegistry>,
lifecycle: FairGateGuard,
) -> Self {
Self {
registry,
bg_registry: Some(bg_registry),
shut_down: AtomicBool::new(false),
_lifecycle: Some(lifecycle),
}
}
#[must_use]
pub fn dummy() -> Self {
Self {
registry: Arc::new(ThreadRegistry::with_seed_and_threads(0, 2)),
bg_registry: None,
shut_down: AtomicBool::new(true),
_lifecycle: None,
}
}
pub fn shutdown(&self) {
tracing::debug!("PoolGuard::shutdown() called - pool shutting down");
if self
.shut_down
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
.is_ok()
{
tracing::debug!("PoolGuard::shutdown() - signaling kill to all threads");
if let Some(ref bg) = self.bg_registry {
tracing::debug!("PoolGuard::shutdown() - shutting down background workers");
if let ShutdownOutcome::TimedOut {
abandoned_jobs,
busy_workers,
} = bg.shutdown()
{
tracing::warn!(
abandoned_jobs,
busy_workers,
"PoolGuard::shutdown() - background shutdown timed out"
);
}
}
self.registry.shutdown();
tracing::debug!("clearing the global pool registry");
clear_global_registries();
tracing::debug!("cleared the global pool registry");
} else {
tracing::debug!("PoolGuard::shutdown() - already shut down, skipping");
}
}
#[must_use]
pub fn waitgroup(&self) -> &WaitGroup {
self.registry.waitgroup()
}
}
impl Drop for PoolGuard {
fn drop(&mut self) {
self.shutdown();
}
}
#[cfg(target_pointer_width = "64")]
const THREADS_BITS: usize = 16;
#[cfg(target_pointer_width = "32")]
const THREADS_BITS: usize = 8;
const THREADS_MAX: usize = (1 << THREADS_BITS) - 1;
pub fn get_allocatable_thread_count() -> usize {
let max_threads = get_max_threads();
let desired_threads = get_num_threads();
assert!(
(desired_threads <= max_threads),
"Desired thread count cant be greater than maximum allowed threads {max_threads}"
);
assert!((desired_threads != 0), "Desired thread count cant be zero");
assert!((max_threads >= 2), "Available thread cant be less than 2 we use 1 thread for the service kill signal, and 1 for tasks");
assert!((desired_threads >= 2), "Requested thread cant be less than 2 we use 1 thread for the service kill signal, and 1 for tasks");
if desired_threads == max_threads {
return max_threads - 1;
}
desired_threads
}
pub fn get_max_threads() -> usize {
let system_value = std::thread::available_parallelism()
.ok()
.map_or(1, std::num::NonZero::get);
tracing::debug!("thread::available_parallelism() reported: {}", system_value);
system_value
}
pub fn get_num_threads() -> usize {
let thread_num = match env::var("VALTRON_NUM_THREADS")
.ok()
.and_then(|s| usize::from_str(&s).ok())
{
Some(x) => {
tracing::debug!("Retrieved thread_number: {x:} from VALTRON_NUM_THREADS");
x
}
_ => get_max_threads(),
};
tracing::debug!("Reporting Thread available for use: {}", thread_num);
thread_num
}
pub struct ThreadYielders {
yielders: RwLock<Vec<Arc<ThreadYielder>>>,
}
impl ThreadYielders {
#[must_use]
pub fn new() -> Self {
Self {
yielders: RwLock::new(Vec::new()),
}
}
pub fn register(&self, yielder: Arc<ThreadYielder>) {
let mut yielders = self.yielders.write().unwrap();
yielders.push(yielder);
}
pub fn interrupt_all(&self) {
let yielders = self.yielders.read().unwrap();
for yielder in yielders.iter() {
yielder.interrupt();
}
}
}
impl Default for ThreadYielders {
fn default() -> Self {
Self::new()
}
}
pub type SharedThreadYielders = sync::Arc<ThreadYielders>;
pub struct ThreadYielder {
thread_id: ThreadId,
latch: Arc<LockSignal>,
sender: mpp::Sender<ThreadActivity>,
condvar: Arc<CondVar>,
wait_state: Arc<CvMutex<WaitState>>,
}
impl std::fmt::Debug for ThreadYielder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ThreadYielder")
.field("thread_id", &self.thread_id)
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum WaitState {
Waiting,
Notified,
}
impl ThreadYielder {
pub fn new(
thread_id: ThreadId,
latch: Arc<LockSignal>,
sender: mpp::Sender<ThreadActivity>,
) -> Self {
Self {
thread_id,
latch,
sender,
condvar: Arc::new(CondVar::new()),
wait_state: Arc::new(CvMutex::new(WaitState::Waiting)),
}
}
pub fn interrupt(&self) {
if let Ok(mut guard) = self.wait_state.lock() {
*guard = WaitState::Notified;
}
self.condvar.notify_all();
}
}
impl Clone for ThreadYielder {
fn clone(&self) -> Self {
Self {
thread_id: self.thread_id.clone(),
latch: self.latch.clone(),
sender: self.sender.clone(),
condvar: self.condvar.clone(),
wait_state: self.wait_state.clone(),
}
}
}
impl ProcessController for ThreadYielder {
type YieldSignal = ();
fn yield_for(&self, dur: std::time::Duration) {
self.sender
.send(ThreadActivity::Parked(self.thread_id.clone()))
.expect("should send event");
let guard = self.wait_state.lock().unwrap();
let (mut guard, result) = self
.condvar
.wait_timeout_while(guard, dur, |state| *state == WaitState::Waiting)
.unwrap();
let _timed_out = result.timed_out();
*guard = WaitState::Waiting;
self.sender
.send(ThreadActivity::Unparked(self.thread_id.clone()))
.expect("should send event");
}
}
#[derive(Clone)]
pub struct SharedThreadRegistry(sync::Arc<RwLock<ThreadPoolRegistryInner>>);
impl SharedThreadRegistry {
pub fn new(inner: sync::Arc<RwLock<ThreadPoolRegistryInner>>) -> Self {
Self(inner)
}
#[must_use]
pub fn executor_count(&self) -> usize {
let registry = self.0.read().unwrap_or_else(|p| p.into_inner());
registry.threads.active_slots()
}
#[must_use]
pub fn get_thread(&self, thread: ThreadId) -> ThreadRef {
let registry = self.0.read().unwrap_or_else(|p| p.into_inner());
match registry.threads.get(thread.get_ref()) {
Some(thread_ref) => thread_ref.clone(),
None => unreachable!("We should never get to a point where a ThreadId is invalid"),
}
}
pub fn register_thread(
&self,
name: String,
thread: ThreadRef,
latch: Arc<LockSignal>,
sender: mpp::Sender<ThreadActivity>,
) -> ThreadId {
let mut registry = self.0.write().unwrap_or_else(|p| p.into_inner());
let entry = registry.threads.insert(thread);
match registry.threads.get_mut(&entry) {
Some(mutable_thread_ref) => {
let thread_id = ThreadId::new(entry, name);
mutable_thread_ref.registry_id = Some(thread_id.clone());
mutable_thread_ref.process =
Some(ThreadYielder::new(thread_id.clone(), latch, sender));
thread_id
}
None => unreachable!("Thread must be registered at the entry key"),
}
}
pub fn unregister_thread(&self, thread_id: ThreadId) {
let mut registry = self.0.write().unwrap_or_else(|p| p.into_inner());
registry.threads.vacate(thread_id.get_ref());
}
pub fn clear(&self) {
let mut registry = self.0.write().unwrap_or_else(|p| p.into_inner());
registry.threads.clear();
}
}
pub type SharedActivityQueue = sync::Arc<ConcurrentQueue<ThreadActivity>>;
pub type SharedThreadYielder = sync::Arc<ThreadYielder>;
#[derive(Clone)]
pub struct ThreadRef {
pub name: String,
pub seed: u64,
pub tasks: Arc<ConcurrentQueue<BoxedSendExecutionIterator>>,
pub registry_id: Option<ThreadId>,
pub register: SharedThreadRegistry,
pub global_kill_signal: sync::Arc<OnSignal>,
pub process: Option<ThreadYielder>,
}
impl ThreadRef {
pub fn new(
seed: u64,
name: String,
queue: Arc<ConcurrentQueue<BoxedSendExecutionIterator>>,
registry_id: Option<ThreadId>,
register: SharedThreadRegistry,
global_kill_signal: sync::Arc<OnSignal>,
) -> ThreadRef {
Self {
seed,
name,
tasks: queue,
register,
registry_id,
global_kill_signal,
process: None,
}
}
}
pub struct ThreadPoolRegistryInner {
threads: EntryList<ThreadRef>,
}
impl ThreadPoolRegistryInner {
#[must_use]
pub fn new() -> SharedThreadRegistry {
SharedThreadRegistry::new(sync::Arc::new(RwLock::new(ThreadPoolRegistryInner {
threads: EntryList::new(),
})))
}
}
pub type ThreadExecutionResult<T> = AnyResult<T, ThreadExecutionError>;
#[derive(From, Debug)]
pub enum ThreadExecutionError {
#[from(ignore)]
FailedStart(SendableBoxedError),
#[from(ignore)]
Panicked(Box<dyn Any + Send>),
}
impl core::error::Error for ThreadExecutionError {}
impl core::fmt::Display for ThreadExecutionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Panicked(_) => write!(f, "ThreadExecutionError(_)"),
_ => write!(f, "{self:?}"),
}
}
}
pub struct ThreadPoolTaskBuilder<
Done: Send + 'static,
Pending: Send + 'static,
Resolver: TaskReadyResolver<Done, Pending> + Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done> + Send + 'static,
> {
tasks: SharedTaskQueue,
latch: Arc<LockSignal>,
yielders: Option<SharedThreadYielders>,
task: Option<Task>,
resolver: Option<Resolver>,
panic_handler: Option<BoxedPanicHandler>,
channel_capacity: Option<usize>,
_marker: PhantomData<(Done, Pending)>,
}
impl<
Done: Send + 'static,
Pending: Send + 'static,
Resolver: TaskReadyResolver<Done, Pending> + Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done> + Send + 'static,
> ThreadPoolTaskBuilder<Done, Pending, Resolver, Task>
{
pub fn new(tasks: SharedTaskQueue, latch: Arc<LockSignal>) -> Self {
Self {
tasks,
latch,
yielders: None,
task: None,
resolver: None,
panic_handler: None,
channel_capacity: None,
_marker: PhantomData,
}
}
#[must_use]
pub fn with_yielders(mut self, yielders: SharedThreadYielders) -> Self {
self.yielders = Some(yielders);
self
}
#[must_use]
pub fn with_task(mut self, task: Task) -> Self {
self.task = Some(task);
self
}
#[allow(clippy::return_self_not_must_use)]
pub fn with_panic_handler<T>(mut self, handler: T) -> Self
where
T: Fn(Box<dyn Any + Send>) + Send + Sync + 'static,
{
self.panic_handler = Some(Box::new(handler));
self
}
#[allow(clippy::return_self_not_must_use)]
pub fn with_resolver(mut self, resolver: Resolver) -> Self {
self.resolver = Some(resolver);
self
}
#[allow(clippy::return_self_not_must_use)]
pub fn with_channel_capacity(mut self, capacity: usize) -> Self {
self.channel_capacity = Some(capacity);
self
}
pub fn ready_iter(
self,
wait_cycle: time::Duration,
) -> AnyResult<NotifyRecvIterator<TaskStatus<Done, Pending>>, ExecutorError> {
let iter_chan: Arc<NotifyQueue<TaskStatus<Done, Pending>>> =
match self.channel_capacity {
Some(capacity) => Arc::new(NotifyQueue::bounded(capacity)),
None => Arc::new(NotifyQueue::unbounded()),
};
let boxed_task = match self.task {
Some(task) => match self.resolver {
None => ReadyConsumingIter::new(task, iter_chan.clone()),
Some(_) => return Err(ExecutorError::NotSupported),
},
None => return Err(ExecutorError::TaskRequired),
};
match self.tasks.push(boxed_task.into()) {
Ok(()) => {
if let Some(ref yielders) = self.yielders {
yielders.interrupt_all();
}
match self.tasks.len() {
1 => self.latch.signal_one(),
_ => self.latch.signal_all(),
}
Ok(NotifyRecvIterator::from_notify_queue(iter_chan, wait_cycle))
}
Err(err) => match err {
PushError::Full(_) => Err(ExecutorError::QueueFull),
PushError::Closed(_) => Err(ExecutorError::QueueClosed),
},
}
}
pub fn stream_iter(
self,
wait_cycle: time::Duration,
) -> AnyResult<NotifyQueueStreamIterator<Done, Pending>, ExecutorError> {
self.stream_iter_with_config(wait_cycle, crate::valtron::executors::DEFAULT_MAX_TURNS)
}
pub fn stream_iter_with_config(
self,
wait_cycle: time::Duration,
max_turns: usize,
) -> AnyResult<NotifyQueueStreamIterator<Done, Pending>, ExecutorError> {
let iter_chan: Arc<NotifyQueue<Stream<Done, Pending>>> = match self.channel_capacity {
Some(capacity) => Arc::new(NotifyQueue::bounded(capacity)),
None => Arc::new(NotifyQueue::unbounded()),
};
let boxed_task = match self.task {
Some(task) => match self.resolver {
None => StreamConsumingIter::new(task, iter_chan.clone()),
Some(_) => return Err(ExecutorError::NotSupported),
},
None => return Err(ExecutorError::TaskRequired),
};
match self.tasks.push(boxed_task.into()) {
Ok(()) => {
if let Some(ref yielders) = self.yielders {
yielders.interrupt_all();
}
match self.tasks.len() {
1 => self.latch.signal_one(),
_ => self.latch.signal_all(),
}
Ok(NotifyQueueStreamIterator::new(
iter_chan, max_turns, wait_cycle,
))
}
Err(err) => match err {
PushError::Full(_) => Err(ExecutorError::QueueFull),
PushError::Closed(_) => Err(ExecutorError::QueueClosed),
},
}
}
pub fn schedule_iter(
self,
wait_cycle: time::Duration,
) -> AnyResult<NotifyRecvIterator<TaskStatus<Done, Pending>>, ExecutorError> {
let iter_chan: Arc<NotifyQueue<TaskStatus<Done, Pending>>> =
match self.channel_capacity {
Some(capacity) => Arc::new(NotifyQueue::bounded(capacity)),
None => Arc::new(NotifyQueue::unbounded()),
};
let boxed_task = match self.task {
Some(task) => match self.resolver {
None => ConsumingIter::new(task, iter_chan.clone()),
Some(_) => return Err(ExecutorError::NotSupported),
},
None => return Err(ExecutorError::TaskRequired),
};
match self.tasks.push(boxed_task.into()) {
Ok(()) => {
if let Some(ref yielders) = self.yielders {
yielders.interrupt_all();
}
match self.tasks.len() {
1 => self.latch.signal_one(),
_ => self.latch.signal_all(),
}
Ok(NotifyRecvIterator::from_notify_queue(iter_chan, wait_cycle))
}
Err(err) => match err {
PushError::Full(_) => Err(ExecutorError::QueueFull),
PushError::Closed(_) => Err(ExecutorError::QueueClosed),
},
}
}
pub fn schedule(self) -> AnyResult<(), ExecutorError> {
let task: BoxedSendExecutionIterator = match self.task {
Some(task) => match self.resolver {
Some(resolver) => {
let mut task_iter = OnNext::new(task, resolver);
if let Some(panic_handler) = self.panic_handler {
task_iter = task_iter.with_panic_handler(panic_handler);
}
Box::new(task_iter)
}
None => {
let mut task_iter = DoNext::new(task);
if let Some(panic_handler) = self.panic_handler {
task_iter = task_iter.with_panic_handler(panic_handler);
}
Box::new(task_iter)
}
},
None => return Err(ExecutorError::TaskRequired),
};
match self.tasks.push(task) {
Ok(()) => {
if let Some(ref yielders) = self.yielders {
yielders.interrupt_all();
}
match self.tasks.len() {
1 => self.latch.signal_one(),
_ => self.latch.signal_all(),
}
Ok(())
}
Err(err) => match err {
PushError::Full(_) => Err(ExecutorError::QueueFull),
PushError::Closed(_) => Err(ExecutorError::QueueClosed),
},
}
}
}
impl<
F,
Done: Send + 'static,
Pending: Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done> + Send + 'static,
> ThreadPoolTaskBuilder<Done, Pending, FnReady<F>, Task>
where
F: Fn(TaskStatus<Done, Pending>, BoxedExecutionEngine) + Send + 'static,
{
pub fn on_next(self, action: F) -> Self {
self.with_resolver(FnReady::new(action))
}
}
impl<
F,
Done: Send + 'static,
Pending: Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done> + Send + 'static,
> ThreadPoolTaskBuilder<Done, Pending, FnMutReady<F>, Task>
where
F: FnMut(TaskStatus<Done, Pending>, BoxedExecutionEngine) + Send + 'static,
{
pub fn on_next_mut(self, action: F) -> Self {
self.with_resolver(FnMutReady::new(action))
}
}
pub struct ThreadRegistry {
pub(crate) shared_tasks: Arc<ConcurrentQueue<BoxedSendExecutionIterator>>,
pub(crate) latch: Arc<LockSignal>,
pub(crate) kill_signal: Arc<OnSignal>,
pub(crate) kill_latch: Arc<LockSignal>,
activity_sender: mpp::Sender<ThreadActivity>,
pub(crate) activity_receiver: mpp::Receiver<ThreadActivity>,
pub(crate) waitgroup: WaitGroup,
rng: Mutex<ChaCha8Rng>,
root: Ctx,
pub(crate) max_threads: usize,
pub(crate) live_threads: Arc<AtomicUsize>,
idle_threads: Arc<AtomicUsize>,
priority: PriorityOrder,
op_read_time: time::Duration,
yield_wait_time: time::Duration,
thread_stack_size: Option<usize>,
thread_max_idle_count: u32,
thread_max_sleep_before_end: u32,
thread_back_off_factor: u32,
thread_back_off_jitter: f32,
thread_back_min_duration: time::Duration,
thread_back_max_duration: time::Duration,
registry: SharedThreadRegistry,
thread_handles: RwLock<HashMap<ThreadId, JoinHandle<ThreadExecutionResult<()>>>>,
yielders: SharedThreadYielders,
}
impl std::fmt::Debug for ThreadRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ThreadRegistry")
.field("shared_tasks", &"<SharedTaskQueue>")
.field("latch", &"<LockSignal>")
.field("kill_signal", &"<OnSignal>")
.field("kill_latch", &"<LockSignal>")
.field("activity_sender", &"<Sender<ThreadActivity>>")
.field("activity_receiver", &"<Receiver<ThreadActivity>>")
.field("waitgroup", &"<WaitGroup>")
.field("rng", &"<Mutex<ChaCha8Rng>>")
.field("max_threads", &self.max_threads)
.field("live_threads", &self.live_threads)
.field("idle_threads", &self.idle_threads)
.field("priority", &self.priority)
.field("op_read_time", &self.op_read_time)
.field("yield_wait_time", &self.yield_wait_time)
.field("thread_stack_size", &self.thread_stack_size)
.field("thread_max_idle_count", &self.thread_max_idle_count)
.field(
"thread_max_sleep_before_end",
&self.thread_max_sleep_before_end,
)
.field("thread_back_off_factor", &self.thread_back_off_factor)
.field("thread_back_off_jitter", &self.thread_back_off_jitter)
.field("thread_back_min_duration", &self.thread_back_min_duration)
.field("thread_back_max_duration", &self.thread_back_max_duration)
.field("registry", &"<SharedThreadRegistry>")
.field("thread_handles", &"<RwLock<HashMap>>")
.finish()
}
}
impl ThreadRegistry {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn new(
seed_for_rng: u64,
num_threads: usize,
priority: PriorityOrder,
op_read_time: time::Duration,
yield_wait: time::Duration,
thread_stack_size: Option<usize>,
thread_max_idle_count: u32,
thread_max_sleep_before_end: u32,
thread_back_off_factor: u32,
thread_back_off_jitter: f32,
thread_back_min_duration: time::Duration,
thread_back_max_duration: time::Duration,
) -> Self {
assert!(
num_threads >= 2,
"Unable to create ThreadRegistry with < 2 threads"
);
assert!(
num_threads <= THREADS_MAX,
"Thread count must not exceed {THREADS_MAX}"
);
let latch = Arc::new(LockSignal::new());
let kill_latch = Arc::new(LockSignal::new());
let shared_tasks: Arc<ConcurrentQueue<BoxedSendExecutionIterator>> =
Arc::new(ConcurrentQueue::unbounded());
let (activity_sender, activity_receiver) = mpp::unbounded::<ThreadActivity>();
Self {
shared_tasks: shared_tasks.clone(),
latch: latch.clone(),
kill_signal: Arc::new(OnSignal::new()),
kill_latch,
activity_sender: activity_sender.clone(),
activity_receiver,
waitgroup: WaitGroup::new(),
rng: Mutex::new(ChaCha8Rng::seed_from_u64(seed_for_rng)),
root: Ctx::new(),
max_threads: num_threads,
live_threads: Arc::new(AtomicUsize::new(0)),
idle_threads: Arc::new(AtomicUsize::new(0)),
priority,
op_read_time,
yield_wait_time: yield_wait,
thread_stack_size,
thread_max_idle_count,
thread_max_sleep_before_end,
thread_back_off_factor,
thread_back_off_jitter,
thread_back_min_duration,
thread_back_max_duration,
registry: SharedThreadRegistry::new(Arc::new(RwLock::new(ThreadPoolRegistryInner {
threads: EntryList::new(),
}))),
thread_handles: RwLock::new(HashMap::new()),
yielders: SharedThreadYielders::new(ThreadYielders::new()),
}
}
#[must_use]
pub fn with_seed_and_threads(seed_from_rng: u64, num_threads: usize) -> Self {
Self::new(
seed_from_rng,
num_threads,
PriorityOrder::Top,
DEFAULT_OP_READ_TIME,
DEFAULT_YIELD_WAIT_TIME,
None,
MAX_ROUNDS_IDLE_COUNT,
MAX_ROUNDS_WHEN_SLEEPING_ENDS,
BACK_OFF_THREAD_FACTOR,
BACK_OFF_JITER,
BACK_OFF_MIN_DURATION,
BACK_OFF_MAX_DURATION,
)
}
#[must_use]
pub fn shared_tasks(&self) -> Arc<ConcurrentQueue<BoxedSendExecutionIterator>> {
self.shared_tasks.clone()
}
#[must_use]
pub fn fork_ctx(&self) -> Ctx {
self.root.fork()
}
#[must_use]
pub fn latch(&self) -> Arc<LockSignal> {
self.latch.clone()
}
#[must_use]
pub fn kill_signal(&self) -> Arc<OnSignal> {
self.kill_signal.clone()
}
#[must_use]
pub fn waitgroup(&self) -> &WaitGroup {
&self.waitgroup
}
pub fn register_yielder(&self, yielder: Arc<ThreadYielder>) {
self.yielders.register(yielder);
}
#[must_use]
pub fn yielders(&self) -> SharedThreadYielders {
Arc::clone(&self.yielders)
}
pub fn interrupt_all_yielders(&self) {
self.yielders.interrupt_all();
}
pub fn shutdown(&self) {
self.kill_signal.turn_on();
self.interrupt_all_yielders();
self.latch.signal_all();
self.waitgroup.wait();
self.join_all_threads();
self.registry.clear();
self.kill_latch.signal_all();
}
pub fn join_all_threads(&self) {
let handles = {
let mut map = self.thread_handles.write().unwrap();
std::mem::take(&mut *map)
};
for (_id, handle) in handles {
let _ = handle.join();
}
}
pub fn block_until_done(&self) {
loop {
if self.listen_for_activity().is_none() {
break;
}
}
self.waitgroup.wait();
self.join_all_threads();
self.kill_latch.signal_all();
}
fn listen_for_activity(&self) -> Option<()> {
let remaining_live = self.live_threads.load(atomic::Ordering::Acquire);
if remaining_live == 0 {
tracing::debug!("No more live threads, returning");
self.latch.signal_all();
return None;
}
if self.kill_signal.probe() {
tracing::debug!("Kill signal detected, waking up");
self.latch.signal_all();
}
let thread_activity = match self.activity_receiver.recv_timeout(self.op_read_time) {
Ok(activity) => activity,
Err(err) => match err {
mpp::ReceiverError::Empty => return Some(()),
mpp::ReceiverError::Timeout => return Some(()),
mpp::ReceiverError::Closed(_) => return None,
},
};
match thread_activity {
ThreadActivity::BroadcastedTask => {
if self.shared_tasks.len() == 1 {
self.latch.signal_one();
}
}
ThreadActivity::Started(_id) => {
tracing::debug!("Thread started: {:?}", _id);
}
ThreadActivity::Stopped(id) => {
tracing::debug!("Thread stopped: {:?}", id);
self.live_threads.fetch_sub(1, atomic::Ordering::AcqRel);
self.registry.unregister_thread(id.clone());
self.remove_thread_id(id);
}
ThreadActivity::Panicked(id, err) => {
tracing::debug!("Thread panicked: {:?}, err: {:?}", id, err);
self.live_threads.fetch_sub(1, atomic::Ordering::AcqRel);
self.registry.unregister_thread(id.clone());
self.remove_thread_id(id);
if self.live_threads.load(atomic::Ordering::Acquire) < self.max_threads
&& !self.kill_signal.probe()
{
tracing::debug!("Respawning thread after panic");
let _ = self.spawn_worker();
}
}
ThreadActivity::Blocked(id) => {
tracing::debug!("Thread blocked: {:?}", id);
self.idle_threads.fetch_add(1, atomic::Ordering::AcqRel);
}
ThreadActivity::Unblocked(id) => {
tracing::debug!("Thread unblocked: {:?}", id);
self.idle_threads.fetch_sub(1, atomic::Ordering::AcqRel);
}
ThreadActivity::Parked(id) => {
tracing::debug!("Thread parked: {:?}", id);
self.idle_threads.fetch_add(1, atomic::Ordering::AcqRel);
}
ThreadActivity::Unparked(id) => {
tracing::debug!("Thread unparked: {:?}", id);
self.idle_threads.fetch_sub(1, atomic::Ordering::AcqRel);
}
}
Some(())
}
fn remove_thread_id(&self, thread_id: ThreadId) {
self.thread_handles.write().unwrap().remove(&thread_id);
}
pub fn spawn_worker(&self) -> ThreadExecutionResult<ThreadRef> {
use std::thread::Builder;
let mut rng = self.rng.lock().unwrap();
let seed = rng.next_u64();
drop(rng);
let b = Builder::new();
let b = if let Some(size) = self.thread_stack_size {
b.stack_size(size)
} else {
b
};
let worker_tag = format!("worker-{seed}");
let thread_ref = ThreadRef::new(
seed,
worker_tag.clone(),
self.shared_tasks.clone(),
None,
self.registry.clone(),
self.kill_signal.clone(),
);
let thread_id = self.registry.register_thread(
thread_ref.name.clone(),
thread_ref.clone(),
self.latch.clone(),
self.activity_sender.clone(),
);
let thread_ref = self.registry.get_thread(thread_id.clone());
let process_clone = thread_ref.process.clone().unwrap();
self.register_yielder(Arc::new(process_clone.clone()));
let priority = self.priority.clone();
let seed_clone = thread_ref.seed;
let task_clone = thread_ref.tasks.clone();
let thread_kill_signal = thread_ref.global_kill_signal.clone();
let sender_id = thread_id.clone();
let sender = self.activity_sender.clone();
let thread_yield_wait_duration = self.yield_wait_time;
let thread_max_idle_count = self.thread_max_idle_count;
let thread_max_sleep_before_end = self.thread_max_sleep_before_end;
let thread_back_off_factor = self.thread_back_off_factor;
let thread_back_off_jitter = self.thread_back_off_jitter;
let thread_back_min_duration = self.thread_back_min_duration;
let thread_back_max_duration = self.thread_back_max_duration;
let wg_guard = self.waitgroup.guard();
self.waitgroup.add(1);
let dispatch = tracing::dispatcher::get_default(|d| d.clone());
match b.spawn(move || {
let _dispatch_guard = tracing::dispatcher::set_default(&dispatch);
let span = tracing::trace_span!("ThreadRegistry::spawn_worker.local_executor.thread");
let _enter = span.enter();
tracing::debug!("worker thread({}) {} started", &worker_tag, seed_clone);
let _wg = wg_guard;
match panic::catch_unwind(|| {
sender
.send(ThreadActivity::Started(sender_id.clone()))
.expect("should send event");
let thread_executor = LocalThreadExecutor::from_seed(
seed_clone,
worker_tag.clone(),
task_clone,
IdleMan::new(
thread_max_idle_count,
None,
SleepyMan::new(
thread_max_sleep_before_end,
ExponentialBackoffDecider::new(
thread_back_off_factor,
thread_back_off_jitter,
thread_back_min_duration,
Some(thread_back_max_duration),
),
),
),
priority,
process_clone,
thread_yield_wait_duration,
Some(thread_kill_signal),
Some(sender.clone()),
);
thread_executor.block_on();
sender
.send(ThreadActivity::Stopped(sender_id.clone()))
.expect("should send event");
tracing::trace!(
"Worker thread({}) {} stopped blocking and shoiuld now stop (ok)",
&worker_tag,
seed_clone
);
}) {
Ok(()) => {
tracing::debug!("worker thread({}) {} stopped cleanly", &worker_tag, seed_clone);
Ok(())
}
Err(err) => {
tracing::error!(
"worker thread({}) {} stopped by panic: {:?}",
&worker_tag,
seed_clone,
err
);
sender
.send(ThreadActivity::Panicked(sender_id.clone(), err))
.expect("should send event");
sender
.send(ThreadActivity::Stopped(sender_id))
.expect("should send event");
Ok(())
}
}
}) {
Ok(handle) => {
self.thread_handles
.write()
.unwrap()
.insert(thread_id.clone(), handle);
self.live_threads.fetch_add(1, atomic::Ordering::AcqRel);
Ok(thread_ref.clone())
}
Err(err) => Err(ThreadExecutionError::FailedStart(Box::new(err))),
}
}
}