use crate::{
RuntimeError,
constants::{DEAD_KQUEUE_ID, RESTART_BACKOFF, RESTART_LIMIT, RESTART_WINDOW},
executor::{self, Executor},
futures::sleep::Sleep,
futures::task::Task,
modules::{
builder::{RuntimeBuilder, TaskBuilder},
handle_kind::HandleKind,
input::{self, Standalone},
int_check::IntCheck,
join_policy::JoinPolicy,
pool_stats::PoolStats,
runtime_status::RuntimeStatus,
task_handle::TaskHandle,
tuning::Tuning,
worker_pool::POOL,
},
reactor::Reactor,
};
use std::{
sync::{
atomic::{AtomicBool, AtomicI32, Ordering},
mpsc,
},
thread,
time::Duration,
time::Instant,
};
static INIT: AtomicBool = AtomicBool::new(false);
static READY: AtomicBool = AtomicBool::new(false);
static REACTOR_KQUEUE_ID: AtomicI32 = AtomicI32::new(0);
pub struct Runtime;
impl Runtime {
pub fn init() -> Result<(), RuntimeError> {
Self::init_with(Tuning::new())
}
pub fn builder() -> RuntimeBuilder {
RuntimeBuilder::new()
}
pub(crate) fn init_with(tuning: Tuning) -> Result<(), RuntimeError> {
if INIT.swap(true, Ordering::SeqCst) {
while !READY.load(Ordering::Acquire) {
thread::yield_now();
}
return Executor::init(tuning);
}
let started = init_runtime(tuning);
READY.store(true, Ordering::Release);
started
}
#[inline(always)]
pub fn block<F>(mut task: F) -> F::Output
where
F: Task,
F::Input: Standalone,
{
task.give(input::token(), input::standalone());
task.prepare(input::token());
let reactor_id = REACTOR_KQUEUE_ID.load(Ordering::Relaxed);
task.execute(input::token(), reactor_id, 0)
}
#[inline(always)]
pub fn task<F>(task: F) -> TaskBuilder<F>
where
F: Task,
{
TaskBuilder::new(task)
}
#[inline(always)]
pub fn sleep(time: Duration) -> Duration {
Self::block(Sleep::sleep(time))
}
pub fn join_all<T, W, I>(handles: I) -> Vec<Result<T, RuntimeError>>
where
I: IntoIterator<Item = TaskHandle<T, W>>,
T: Clone,
W: HandleKind,
{
handles.into_iter().map(|handle| handle.join()).collect()
}
pub fn join_first<T, W, I>(
handles: I,
policy: JoinPolicy,
) -> (TaskHandle<T, W>, Option<Vec<TaskHandle<T, W>>>)
where
I: IntoIterator<Item = TaskHandle<T, W>>,
W: HandleKind,
{
let mut handles: Vec<TaskHandle<T, W>> = handles.into_iter().collect();
let ids: Vec<usize> = handles.iter().map(|handle| handle.id()).collect();
let winner = match executor::join_first(&ids) {
Some(winner) => winner,
None => {
return (
TaskHandle::detached(),
match policy {
JoinPolicy::PassBack => Some(Vec::new()),
_ => None,
},
);
}
};
let at = ids.iter().position(|id| *id == winner).unwrap_or_default();
let first = handles.remove(at);
match policy {
JoinPolicy::PassBack => (first, Some(handles)),
JoinPolicy::Cancel => {
for handle in handles {
handle.cancel();
}
(first, None)
}
JoinPolicy::Drop => {
drop(handles);
(first, None)
}
}
}
pub fn initialised() -> bool {
READY.load(Ordering::Acquire)
}
pub fn healthy() -> bool {
Self::status().healthy()
}
pub fn status() -> RuntimeStatus {
let initialised = Self::initialised();
RuntimeStatus::new(
initialised,
initialised && executor::shutting_down(),
initialised && REACTOR_KQUEUE_ID.load(Ordering::Relaxed) != DEAD_KQUEUE_ID,
initialised && executor::manager_alive(),
initialised && POOL.live() > 0 && POOL.dead() == 0,
)
}
pub fn shutdown() {
executor::shutdown_now();
}
pub fn trim() -> Result<usize, RuntimeError> {
executor::trim()
}
#[cfg(feature = "fault-injection")]
#[doc(hidden)]
pub fn inject_manager_faults(count: u32) {
executor::inject_manager_faults(count);
}
#[cfg(feature = "fault-injection")]
#[doc(hidden)]
pub fn inject_thread_deaths(workers: u32, sleep_threads: u32) {
crate::modules::faults::owe_thread_deaths(workers, sleep_threads);
POOL.wake_everyone();
}
#[cfg(feature = "fault-injection")]
#[doc(hidden)]
pub fn inject_spawn_refusals(count: u32) {
crate::modules::faults::owe_spawn_refusals(count);
}
#[cfg(feature = "fault-injection")]
#[doc(hidden)]
pub fn inject_help_depth(depth: usize) {
crate::modules::help::limit_depth(depth);
}
pub fn pool() -> PoolStats {
POOL.stats()
}
#[inline(always)]
pub(crate) fn reactor_id() -> i32 {
REACTOR_KQUEUE_ID.load(Ordering::Relaxed)
}
}
fn init_runtime(tuning: Tuning) -> Result<(), RuntimeError> {
let reactor_id = unsafe { libc::kqueue() }.check()?;
REACTOR_KQUEUE_ID.store(reactor_id, Ordering::SeqCst);
thread::spawn(move || {
let (tx, rx) = mpsc::channel();
Reactor::init(reactor_id, tx.clone());
let mut failures = 0;
let mut started = Instant::now();
for dead_id in rx {
if started.elapsed() >= RESTART_WINDOW {
failures = 0;
}
failures += 1;
if failures > RESTART_LIMIT {
REACTOR_KQUEUE_ID.store(DEAD_KQUEUE_ID, Ordering::SeqCst);
let _ = unsafe { libc::close(dead_id) };
break;
}
thread::sleep(RESTART_BACKOFF * failures);
let new_id = match unsafe { libc::kqueue() }.check() {
Ok(new_id) => new_id,
Err(_) => {
REACTOR_KQUEUE_ID.store(DEAD_KQUEUE_ID, Ordering::SeqCst);
let _ = unsafe { libc::close(dead_id) };
break;
}
};
REACTOR_KQUEUE_ID.store(new_id, Ordering::SeqCst);
Reactor::init(new_id, tx.clone());
let _ = unsafe { libc::close(dead_id) };
started = Instant::now();
}
});
Executor::init(tuning)
}