#![allow(clippy::type_complexity)]
mod pool_guard;
pub use pool_guard::PoolGuard;
use std::cell::OnceCell;
use std::sync::Arc;
use crate::valtron::{
executors::constants::{
BACK_OFF_JITER, BACK_OFF_MAX_DURATION, BACK_OFF_MIN_DURATION, BACK_OFF_THREAD_FACTOR,
DEFAULT_YIELD_WAIT_TIME, MAX_ROUNDS_IDLE_COUNT, MAX_ROUNDS_WHEN_SLEEPING_ENDS,
},
Ctx, LocalThreadExecutor, PriorityOrder, ProcessController, ProgressIndicator,
SharedTaskQueue, TaskIterator, TaskReadyResolver, TaskSpawnConfig,
};
use crate::{
retries::ExponentialBackoffDecider,
synca::{IdleMan, SleepyMan},
};
use concurrent_queue::ConcurrentQueue;
#[cfg(target_family = "wasm")]
use foundation_nostd::primitives::SpinWaiter;
#[derive(Clone)]
pub struct NoThreadController {
#[cfg(target_family = "wasm")]
waiter: SpinWaiter,
}
impl NoThreadController {
#[must_use]
pub fn new() -> Self {
Self {
#[cfg(target_family = "wasm")]
waiter: SpinWaiter::wasm(),
}
}
}
impl Default for NoThreadController {
fn default() -> Self {
Self::new()
}
}
impl ProcessController for NoThreadController {
type YieldSignal = ();
fn yield_for(&self, dur: std::time::Duration) {
#[cfg(target_family = "wasm")]
self.waiter.wait(dur);
#[cfg(not(target_family = "wasm"))]
std::thread::park_timeout(dur);
}
}
#[cfg(all(
target_family = "wasm",
any(feature = "js-wasmbindgen", feature = "js-foundation-wasm")
))]
use crate::valtron::wasm::JSThreadYielder as DefaultController;
#[cfg(not(all(
target_family = "wasm",
any(feature = "js-wasmbindgen", feature = "js-foundation-wasm")
)))]
type DefaultController = NoThreadController;
thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static GLOBAL_LOCAL_EXECUTOR_ENGINE: OnceCell<LocalThreadExecutor<DefaultController>> = OnceCell::new();
}
static VALTRON_SINGLETON: std::sync::Mutex<Option<PoolGuard>> = std::sync::Mutex::new(None);
pub struct SingleExecutorSingleton;
impl SingleExecutorSingleton {
pub fn get_or_init<F: FnOnce(&PoolGuard)>(seed: u64, setup: F) -> PoolGuard {
initialize_pool(seed);
let mut guard = VALTRON_SINGLETON.lock().unwrap();
if guard.is_none() {
*guard = Some(PoolGuard);
}
setup(guard.as_ref().unwrap());
guard.clone().unwrap()
}
pub fn guard() -> PoolGuard {
VALTRON_SINGLETON
.lock()
.unwrap()
.clone()
.expect("Valtron not initialized — call SingleExecutorSingleton::get_or_init() first")
}
pub fn is_initialized() -> bool {
VALTRON_SINGLETON.lock().unwrap().is_some()
}
}
pub fn initialize_pool(seed_for_rng: u64) {
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| {
let _ = pool.get_or_init(|| {
#[allow(clippy::arc_with_non_send_sync)]
let tasks: SharedTaskQueue = Arc::new(ConcurrentQueue::unbounded());
LocalThreadExecutor::from_seed(
seed_for_rng,
"single-thread-pool".to_string(),
tasks,
IdleMan::new(
MAX_ROUNDS_IDLE_COUNT,
None,
SleepyMan::new(
MAX_ROUNDS_WHEN_SLEEPING_ENDS,
ExponentialBackoffDecider::new(
BACK_OFF_THREAD_FACTOR,
BACK_OFF_JITER,
BACK_OFF_MIN_DURATION,
Some(BACK_OFF_MAX_DURATION),
),
),
),
PriorityOrder::Top,
DefaultController::new(),
DEFAULT_YIELD_WAIT_TIME,
None,
None,
)
});
});
}
#[must_use]
pub fn has_runnable_work() -> bool {
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| {
pool.get()
.is_some_and(LocalThreadExecutor::has_runnable_work)
})
}
pub fn run_until<S>(checker: S)
where
S: Fn(ProgressIndicator) -> bool,
{
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| match pool.get() {
Some(pool) => pool.run_until(checker),
None => panic!("Thread pool not initialized, ensure to call initialize() first"),
});
}
#[must_use]
pub fn fork_ctx() -> Ctx {
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| match pool.get() {
Some(pool) => pool.fork_ctx(),
None => panic!("Thread pool not initialized, ensure to call initialize() first"),
})
}
#[must_use]
pub fn run_once() -> ProgressIndicator {
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| match pool.get() {
Some(pool) => pool.run_once(),
None => panic!("Thread pool not initialized, ensure to call initialize() first"),
})
}
pub fn run_until_complete() {
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| match pool.get() {
Some(pool) => pool.block_until_finished(),
None => panic!("Thread pool not initialized, ensure to call initialize() first"),
});
}
#[must_use]
pub fn spawn<Task>() -> TaskSpawnConfig<
Task::Ready,
Task::Pending,
Box<dyn TaskReadyResolver<Task::Ready, Task::Pending> + 'static>,
Task,
>
where
Task::Ready: 'static,
Task::Pending: 'static,
Task: TaskIterator + 'static,
{
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| match pool.get() {
Some(pool) => TaskSpawnConfig::new(pool.boxed_engine()),
None => panic!("Thread pool not initialized, ensure to call initialize() first"),
})
}
#[must_use]
pub fn spawn2<Task, Resolver>(
) -> TaskSpawnConfig<Task::Ready, Task::Pending, Resolver, Task>
where
Task::Ready: 'static,
Task::Pending: 'static,
Task: TaskIterator + 'static,
Resolver: TaskReadyResolver<Task::Ready, Task::Pending> + 'static,
{
GLOBAL_LOCAL_EXECUTOR_ENGINE.with(|pool| match pool.get() {
Some(pool) => TaskSpawnConfig::new(pool.boxed_engine()),
None => panic!("Thread pool not initialized, ensure to call initialize() first"),
})
}
pub fn run_background_job(job: impl FnOnce() + 'static) -> crate::valtron::GenericResult<()> {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(job));
match result {
Ok(()) => Ok(()),
Err(panic_info) => {
tracing::error!("Background job panicked (single-threaded): {panic_info:?}");
Err("Background job panicked".into())
}
}
}