use std::any::Any;
use std::cell::RefCell;
use std::future::Future;
use std::io;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Wake, Waker};
use std::time::Duration;
use super::driver_backend::{DriverBackend, Notifier};
use super::future_task::{FutureTask, JoinState, TaskShared};
use super::handles::{
IntervalHandle, JoinHandle, ThreadHandle, TimeoutHandle, WorkerHandle, YieldNow,
};
use super::state::{
ChildWorker, IntervalEntry, MacroTask, ThreadShared, WorkerCompletion, describe_panic,
install_thread, lock_queue, teardown_thread, try_with_installed_thread, with_current_thread,
with_installed_thread,
};
use super::timer::{TimerKind, TimerNode};
use super::{IntervalCallback, LocalTask, MICROTASK_STARVATION_THRESHOLD};
use crate::trace_targets;
#[doc(hidden)]
pub trait Runtime: 'static {
fn create_driver_pair() -> io::Result<(Box<dyn DriverBackend>, Box<dyn Notifier>)>;
fn monotonic_now() -> io::Result<Duration>;
}
pub fn current_thread_handle<R: Runtime>() -> ThreadHandle {
with_current_thread::<R, _>(|state| state.handle())
}
pub(crate) fn try_current_thread_handle() -> Option<ThreadHandle> {
try_with_installed_thread(|state| state.map(|s| s.handle()))
}
pub(crate) fn with_current_driver_any<R: Runtime, T: Any, U>(f: impl FnOnce(&T) -> U) -> U {
with_current_thread::<R, _>(|state| {
let any = state.driver.as_any();
let typed = any
.downcast_ref::<T>()
.expect("driver type mismatch in with_current_driver");
f(typed)
})
}
pub fn queue_task<R: Runtime, F>(task: F)
where
F: FnOnce() + 'static,
{
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::SCHEDULER,
event = "queue_task",
queue = "local_macro",
"queueing local macrotask"
);
push_local_macrotask::<R>(Box::new(task));
}
pub fn queue_microtask<R: Runtime, F>(task: F)
where
F: FnOnce() + 'static,
{
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::SCHEDULER,
event = "queue_microtask",
queue = "local_micro",
"queueing local microtask"
);
with_current_thread::<R, _>(|state| {
state
.local_microtasks
.borrow_mut()
.push_back(Box::new(task));
});
}
pub fn timeout<R: Runtime, F>(delay: Duration, callback: F) -> TimeoutHandle
where
F: FnOnce() + 'static,
{
let id = allocate_timer_id::<R>();
let deadline = deadline_from_now::<R>(delay);
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::TIMER,
event = "timeout",
timer_id = id,
delay_ns = delay.as_nanos() as u64,
deadline_ns = deadline.as_nanos() as u64,
"scheduling timeout"
);
let timer = TimerNode::timeout(id, deadline, Box::new(callback));
let generation = with_current_thread::<R, _>(|state| {
state.timers.borrow_mut().insert(timer);
state.generation
});
rearm_thread_timer::<R>();
TimeoutHandle { id, generation }
}
pub fn cancel_timeout(handle: &TimeoutHandle) {
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::TIMER,
event = "cancel_timeout",
timer_id = handle.id,
"cancelling timeout"
);
clear_timer(handle.generation, handle.id);
}
pub fn interval<R: Runtime, F>(delay: Duration, callback: F) -> IntervalHandle
where
F: FnMut() + 'static,
{
let id = allocate_timer_id::<R>();
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::TIMER,
event = "interval",
timer_id = id,
delay_ns = delay.as_nanos() as u64,
"scheduling interval"
);
let callback: IntervalCallback = Rc::new(RefCell::new(Box::new(callback)));
let generation = with_current_thread::<R, _>(|state| {
state.live_intervals.borrow_mut().insert(
id,
IntervalEntry {
callback: Rc::clone(&callback),
interval: delay,
},
);
state.generation
});
if delay.is_zero() {
let scheduled = deadline_from_now::<R>(Duration::ZERO);
schedule_interval_macrotask::<R>(id, scheduled);
} else {
let deadline = deadline_from_now::<R>(delay);
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::TIMER,
event = "interval_deadline",
timer_id = id,
deadline_ns = deadline.as_nanos() as u64,
"interval deadline computed"
);
let timer = TimerNode::interval(id, deadline);
with_current_thread::<R, _>(|state| state.timers.borrow_mut().insert(timer));
rearm_thread_timer::<R>();
}
IntervalHandle { id, generation }
}
pub fn cancel_interval(handle: &IntervalHandle) {
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::TIMER,
event = "cancel_interval",
timer_id = handle.id,
"cancelling interval"
);
clear_timer(handle.generation, handle.id);
}
pub fn queue_future<R: Runtime, F>(future: F) -> JoinHandle<F::Output>
where
F: Future + 'static,
F::Output: 'static,
{
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::ASYNC,
event = "queue_future",
"queueing local future"
);
let (id, owner) = with_current_thread::<R, _>(|state| {
let id = state.next_task_id.get();
state
.next_task_id
.set(id.checked_add(1).expect("task ID space exhausted"));
(id, state.handle())
});
let shared = Rc::new(TaskShared::new());
let state = Rc::new(JoinState::new(Rc::clone(&shared)));
let completion = Rc::clone(&state);
let task = FutureTask::new(
Box::pin(async move {
let output = future.await;
completion.complete(output);
}),
Rc::clone(&shared),
id,
owner,
);
shared.set_task(&task);
with_current_thread::<R, _>(|state| {
state.tasks.borrow_mut().insert(id, Rc::clone(&task));
});
task.schedule();
JoinHandle { state }
}
pub fn spawn_worker<R: Runtime, Init, Exit>(initial_task: Init, on_exit: Exit) -> WorkerHandle
where
Init: FnOnce() + Send + 'static,
Exit: FnOnce() + 'static,
{
tracing::debug!(
target: trace_targets::RUNTIME,
event = "spawn_worker",
"spawning runtime worker thread"
);
let (driver, notifier) = R::create_driver_pair().expect("worker driver should initialize");
let shared = Arc::new(ThreadShared::new(notifier));
let handle = ThreadHandle {
shared: Arc::clone(&shared),
};
let completion = Arc::new(WorkerCompletion {
finished: AtomicBool::new(false),
parent_event: with_current_thread::<R, _>(|parent| parent.handle()),
});
with_current_thread::<R, _>(|parent| {
parent.children.borrow_mut().push(ChildWorker {
completion: Arc::clone(&completion),
on_exit: Some(Box::new(on_exit)),
});
});
let worker_completion = Arc::clone(&completion);
std::thread::Builder::new()
.name("runite-worker".into())
.spawn(move || {
let panic_completion = Arc::clone(&worker_completion);
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
install_thread(shared, driver, Some(worker_completion));
queue_task::<R, _>(initial_task);
run::<R>();
}));
if outcome.is_err() {
panic_completion.finished.store(true, Ordering::Release);
panic_completion.parent_event.shared.notify();
}
})
.expect("worker thread should spawn");
WorkerHandle {
thread: handle,
completion,
}
}
pub fn yield_now() -> YieldNow {
YieldNow { yielded: false }
}
pub fn run<R: Runtime>() {
let _span = tracing::debug_span!(
target: trace_targets::RUNTIME,
"runtime.run"
)
.entered();
tracing::debug!(
target: trace_targets::RUNTIME,
event = "run_enter",
"entering runtime event loop"
);
with_current_thread::<R, _>(|_| {});
let _event_loop = EventLoopGuard::enter();
loop {
drain_all::<R>();
drain_microtasks::<R>();
if let Some(task) = pop_macrotask::<R>() {
run_guarded(task);
continue;
}
drain_all::<R>();
if has_ready_work() {
continue;
}
if !with_installed_thread(|state| state.try_begin_shutdown()) {
continue;
}
let mut closing_reset = ClosingResetGuard::new();
drain_all::<R>();
if has_ready_work() {
continue;
}
let busy = with_installed_thread(|state| {
!state.timers.borrow().is_empty()
|| state.has_live_children()
|| state.has_live_async_operations()
});
if busy {
with_installed_thread(|state| {
state.shared.closing.store(false, Ordering::Release);
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::RUNTIME,
event = "run_wait",
pending_timers = !state.timers.borrow().is_empty(),
live_children = state.has_live_children(),
live_async = state.has_live_async_operations(),
"runtime waiting for more work"
);
state.driver.wait().expect("driver wait should succeed");
});
continue;
}
let (committed, worker_completion) = with_installed_thread(|state| {
let remote = lock_queue(&state.shared.remote_macrotasks);
if remote.is_empty() {
state.shared.closed.store(true, Ordering::Release);
(true, state.worker_completion.clone())
} else {
(false, None)
}
});
if !committed {
continue;
}
closing_reset.disarm();
if let Some(completion) = worker_completion {
completion.finished.store(true, Ordering::Release);
completion.parent_event.shared.notify();
}
with_installed_thread(|state| state.shared.notify());
tracing::debug!(
target: trace_targets::RUNTIME,
event = "run_exit",
"runtime event loop exiting"
);
teardown_thread();
return;
}
}
pub fn run_until_stalled<R: Runtime>() {
with_current_thread::<R, _>(|_| {});
let _event_loop = EventLoopGuard::enter();
loop {
drain_all::<R>();
drain_microtasks::<R>();
if let Some(task) = pop_macrotask::<R>() {
run_guarded(task);
continue;
}
drain_all::<R>();
if has_ready_work() {
continue;
}
with_installed_thread(|state| {
state.shared.closing.store(false, Ordering::Release);
});
return;
}
}
pub fn run_ready_tasks<R: Runtime>() {
with_current_thread::<R, _>(|_| {});
let _event_loop = EventLoopGuard::enter();
loop {
drain_remote_tasks::<R>();
drain_completed_workers::<R>();
drain_microtasks::<R>();
if let Some(task) = pop_macrotask::<R>() {
run_guarded(task);
continue;
}
drain_remote_tasks::<R>();
drain_completed_workers::<R>();
if has_ready_work() {
continue;
}
with_installed_thread(|state| {
state.shared.closing.store(false, Ordering::Release);
});
return;
}
}
pub fn block_on<R: Runtime, F: Future>(future: F) -> F::Output {
with_current_thread::<R, _>(|_| {});
let _event_loop = EventLoopGuard::enter();
let owner = with_installed_thread(|state| state.handle());
let block_waker = Arc::new(BlockOnWaker {
woken: AtomicBool::new(true),
owner,
});
let waker = Waker::from(Arc::clone(&block_waker));
let mut context = Context::from_waker(&waker);
let mut future = core::pin::pin!(future);
loop {
if block_waker.woken.swap(false, Ordering::AcqRel)
&& let Poll::Ready(output) = future.as_mut().poll(&mut context)
{
return output;
}
drain_all::<R>();
drain_microtasks::<R>();
if let Some(task) = pop_macrotask::<R>() {
run_guarded(task);
continue;
}
drain_all::<R>();
if block_waker.woken.load(Ordering::Acquire) || has_ready_work() {
continue;
}
with_installed_thread(|state| {
state.driver.wait().expect("driver wait should succeed");
});
}
}
struct BlockOnWaker {
woken: AtomicBool,
owner: ThreadHandle,
}
impl Wake for BlockOnWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.woken.store(true, Ordering::Release);
if !self.owner.is_current() {
self.owner.shared.notify();
}
}
}
fn drain_microtasks<R: Runtime>() {
let mut microtasks_run: u64 = 0;
let mut warned = false;
while let Some(task) = pop_microtask() {
run_guarded(task);
microtasks_run += 1;
if !warned
&& microtasks_run.is_multiple_of(MICROTASK_STARVATION_THRESHOLD)
&& macrotask_waiting::<R>()
{
warned = true;
tracing::warn!(
target: trace_targets::SCHEDULER,
event = "microtask_starvation",
threshold = MICROTASK_STARVATION_THRESHOLD,
microtasks_run,
"a single microtask checkpoint has run {microtasks_run} tasks without yielding while macrotasks (timers, I/O, cross-thread work) are waiting; macrotask handlers are being starved",
);
}
}
}
fn macrotask_waiting<R: Runtime>() -> bool {
let now = deadline_from_now::<R>(Duration::ZERO);
with_installed_thread(|state| {
!state.local_macrotasks.borrow().is_empty()
|| state
.timers
.borrow()
.peek_deadline()
.is_some_and(|deadline| deadline <= now)
|| !lock_queue(&state.shared.remote_macrotasks).is_empty()
})
}
fn run_guarded(task: LocalTask) {
if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(task)) {
tracing::error!(
target: trace_targets::SCHEDULER,
event = "scheduled_task_panicked",
panic = describe_panic(&*payload),
"scheduled task panicked; isolating panic to keep the event loop running",
);
}
}
struct EventLoopGuard;
impl EventLoopGuard {
fn enter() -> Self {
with_installed_thread(|state| {
assert!(
!state.in_event_loop.replace(true),
"runite: cannot re-enter the runtime event loop; `run`, \
`run_until_stalled`, and `run_ready_tasks` must not be called from \
within a task or callback already running on this runtime thread",
);
});
EventLoopGuard
}
}
impl Drop for EventLoopGuard {
fn drop(&mut self) {
try_with_installed_thread(|state| {
if let Some(state) = state {
state.in_event_loop.set(false);
}
});
}
}
struct ClosingResetGuard {
armed: bool,
}
impl ClosingResetGuard {
fn new() -> Self {
Self { armed: true }
}
fn disarm(&mut self) {
self.armed = false;
}
}
impl Drop for ClosingResetGuard {
fn drop(&mut self) {
if !self.armed {
return;
}
try_with_installed_thread(|state| {
if let Some(state) = state {
state.shared.closing.store(false, Ordering::Release);
}
});
}
}
fn drain_all<R: Runtime>() {
drain_driver_events::<R>();
drain_remote_tasks::<R>();
drain_completed_workers::<R>();
}
fn drain_driver_events<R: Runtime>() {
loop {
let ready =
with_installed_thread(|state| state.driver.poll().expect("driver poll should succeed"));
let Some(ready) = ready else {
break;
};
if ready.wake {
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::DRIVER,
event = "drain_wake",
"draining driver wake notifications"
);
with_installed_thread(|state| {
let _ = state.driver.drain_wake();
});
}
if ready.timer {
#[cfg(debug_assertions)]
tracing::trace!(
target: trace_targets::TIMER,
event = "drain_timer",
"draining expired runtime timers"
);
with_installed_thread(|state| {
let _ = state.driver.drain_timer();
});
dispatch_expired_timers::<R>();
}
}
}
fn drain_remote_tasks<R: Runtime>() {
let drained = with_installed_thread(|state| {
let mut remote = lock_queue(&state.shared.remote_macrotasks);
std::mem::take(&mut *remote)
});
if !drained.is_empty() {
with_installed_thread(move |state| {
let mut local = state.local_macrotasks.borrow_mut();
for task in drained {
let task: LocalTask = task;
local.push_back(make_macro_task::<R>(task));
}
});
}
}
fn drain_completed_workers<R: Runtime>() {
let exited = with_installed_thread(|state| {
let mut exited = Vec::new();
let mut children = state.children.borrow_mut();
let mut index = 0;
while index < children.len() {
if children[index].completion.finished.load(Ordering::Acquire) {
let child = children.swap_remove(index);
exited.push(child);
} else {
index += 1;
}
}
exited
});
if exited.is_empty() {
return;
}
with_installed_thread(move |state| {
let mut local = state.local_macrotasks.borrow_mut();
for mut child in exited {
if let Some(task) = child.on_exit.take() {
local.push_back(make_macro_task::<R>(task));
}
}
});
}
fn pop_microtask() -> Option<LocalTask> {
with_installed_thread(|state| state.local_microtasks.borrow_mut().pop_front())
}
fn pop_macrotask<R: Runtime>() -> Option<LocalTask> {
let entry = with_installed_thread(|state| state.local_macrotasks.borrow_mut().pop_front())?;
#[cfg(debug_assertions)]
{
let now = deadline_from_now::<R>(Duration::ZERO);
let wait = now.saturating_sub(entry.queued_at);
tracing::trace!(
target: trace_targets::SCHEDULER,
event = "macrotask_dequeued",
wait_ns = wait.as_nanos() as u64,
"macrotask dequeued after waiting in queue"
);
}
let _phantom: core::marker::PhantomData<R> = core::marker::PhantomData;
Some(entry.task)
}
fn push_local_macrotask<R: Runtime>(task: LocalTask) {
with_current_thread::<R, _>(|state| {
state
.local_macrotasks
.borrow_mut()
.push_back(make_macro_task::<R>(task));
});
}
fn make_macro_task<R: Runtime>(task: LocalTask) -> MacroTask {
let _phantom: core::marker::PhantomData<R> = core::marker::PhantomData;
MacroTask {
task,
#[cfg(debug_assertions)]
queued_at: deadline_from_now::<R>(Duration::ZERO),
}
}
fn has_ready_work() -> bool {
with_installed_thread(|state| {
if !state.local_microtasks.borrow().is_empty()
|| !state.local_macrotasks.borrow().is_empty()
{
return true;
}
if !lock_queue(&state.shared.remote_macrotasks).is_empty() {
return true;
}
false
})
}
fn allocate_timer_id<R: Runtime>() -> usize {
with_current_thread::<R, _>(|state| {
let id = state.next_timer_id.get();
let next = id.checked_add(1).expect("timer ID space exhausted");
state.next_timer_id.set(next);
id
})
}
fn clear_timer(generation: u64, id: usize) {
let should_rearm = try_with_installed_thread(|state| {
let Some(state) = state else {
return false;
};
if state.generation != generation {
return false;
}
state.live_intervals.borrow_mut().remove(&id);
state.timers.borrow_mut().remove(id).is_some()
});
if should_rearm {
rearm_thread_timer_installed();
}
}
fn schedule_interval_macrotask<R: Runtime>(id: usize, scheduled_deadline: Duration) {
push_local_macrotask::<R>(Box::new(move || {
let Some(callback) = with_installed_thread(|state| {
state
.live_intervals
.borrow()
.get(&id)
.map(|entry| Rc::clone(&entry.callback))
}) else {
return;
};
(callback.borrow_mut())();
let interval = match with_installed_thread(|state| {
state
.live_intervals
.borrow()
.get(&id)
.map(|entry| entry.interval)
}) {
Some(interval) => interval,
None => return,
};
let next_deadline = scheduled_deadline
.checked_add(interval)
.unwrap_or(Duration::MAX);
let now = deadline_from_now::<R>(Duration::ZERO);
if now >= next_deadline {
schedule_interval_macrotask::<R>(id, next_deadline);
} else {
let node = TimerNode::interval(id, next_deadline);
with_installed_thread(|state| state.timers.borrow_mut().insert(node));
rearm_thread_timer_installed();
}
}));
}
fn dispatch_expired_timers<R: Runtime>() {
let now = deadline_from_now::<R>(Duration::ZERO);
let due = with_installed_thread(|state| state.timers.borrow_mut().pop_due(now));
if due.is_empty() {
rearm_thread_timer_installed();
return;
}
for timer in due {
match timer.kind {
TimerKind::Timeout(callback) => push_local_macrotask::<R>(callback),
TimerKind::Interval => {
schedule_interval_macrotask::<R>(timer.id, timer.deadline);
}
}
}
rearm_thread_timer_installed();
}
fn rearm_thread_timer<R: Runtime>() {
with_current_thread::<R, _>(|state| {
let deadline = state.timers.borrow().peek_deadline();
state
.driver
.rearm_timer(deadline)
.expect("driver timer rearm should succeed");
});
}
fn rearm_thread_timer_installed() {
with_installed_thread(|state| {
let deadline = state.timers.borrow().peek_deadline();
state
.driver
.rearm_timer(deadline)
.expect("driver timer rearm should succeed");
});
}
fn deadline_from_now<R: Runtime>(delay: Duration) -> Duration {
R::monotonic_now()
.expect("monotonic clock should be available")
.checked_add(delay)
.unwrap_or(Duration::MAX)
}
#[cfg(test)]
mod tests {
use std::io;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use super::super::driver_backend::Notifier;
use super::super::handles::QueueError;
use super::*;
struct TestNotifier;
impl Notifier for TestNotifier {
fn notify(&self) -> io::Result<()> {
Ok(())
}
}
fn handle_with_capacity(capacity: usize) -> ThreadHandle {
ThreadHandle {
shared: Arc::new(ThreadShared::with_remote_capacity(
Box::new(TestNotifier),
capacity,
)),
}
}
#[test]
fn bounded_remote_queue_accepts_up_to_capacity() {
let handle = handle_with_capacity(4);
for _ in 0..4 {
assert!(handle.queue_macrotask(|| {}).is_ok());
}
assert!(matches!(
handle.queue_macrotask(|| {}),
Err(QueueError::Full)
));
}
#[test]
fn closed_thread_returns_closed_error() {
let handle = handle_with_capacity(4);
handle.shared.closed.store(true, Ordering::Release);
assert!(matches!(
handle.queue_macrotask(|| {}),
Err(QueueError::Closed)
));
}
#[test]
fn internal_wakes_bypass_remote_queue_capacity() {
let handle = handle_with_capacity(1);
assert!(handle.queue_macrotask(|| {}).is_ok());
assert!(matches!(
handle.queue_macrotask(|| {}),
Err(QueueError::Full)
));
assert!(handle.queue_internal_wake(|| {}).is_ok());
assert!(handle.queue_internal_wake(|| {}).is_ok());
}
#[test]
fn internal_wakes_still_reject_when_closed() {
let handle = handle_with_capacity(1);
handle.shared.closed.store(true, Ordering::Release);
assert!(matches!(
handle.queue_internal_wake(|| {}),
Err(QueueError::Closed)
));
}
}