use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::io;
use std::ptr;
use std::rc::Rc;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use super::driver_backend::{DriverBackend, Notifier};
use super::future_task::FutureTask;
use super::handles::QueueError;
use super::scheduler::Runtime;
use super::timer::TimerHeap;
use super::{LocalTask, LocalTaskQueue, MacroTaskQueue, SendTask};
use crate::trace_targets;
static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
static REMOTE_QUEUE_CAPACITY: OnceLock<usize> = OnceLock::new();
const DEFAULT_REMOTE_QUEUE_CAPACITY: usize = 65_536;
const MAX_REMOTE_QUEUE_CAPACITY: usize = 1 << 24;
thread_local! {
pub(super) static CURRENT_THREAD: Cell<*mut ThreadState> = const { Cell::new(ptr::null_mut()) };
}
pub(crate) struct MacroTask {
pub(crate) task: LocalTask,
#[cfg(debug_assertions)]
pub(crate) queued_at: Duration,
}
pub(crate) struct IntervalEntry {
pub(crate) callback: super::IntervalCallback,
pub(crate) interval: Duration,
}
pub(crate) type LiveIntervals = RefCell<HashMap<usize, IntervalEntry>>;
pub(crate) struct RemoteQueue {
inner: Mutex<VecDeque<SendTask>>,
capacity: usize,
warned_full: AtomicBool,
}
impl RemoteQueue {
fn new(capacity: usize) -> Self {
Self {
inner: Mutex::new(VecDeque::new()),
capacity: capacity.clamp(1, MAX_REMOTE_QUEUE_CAPACITY),
warned_full: AtomicBool::new(false),
}
}
}
pub(crate) struct ThreadState {
pub(crate) driver: Box<dyn DriverBackend>,
pub(crate) shared: Arc<ThreadShared>,
pub(crate) worker_completion: Option<Arc<WorkerCompletion>>,
pub(crate) local_microtasks: RefCell<LocalTaskQueue>,
pub(crate) local_macrotasks: RefCell<MacroTaskQueue<MacroTask>>,
pub(crate) timers: RefCell<TimerHeap>,
pub(crate) live_intervals: LiveIntervals,
pub(crate) next_timer_id: Cell<usize>,
pub(crate) tasks: RefCell<HashMap<u64, Rc<FutureTask>>>,
pub(crate) next_task_id: Cell<u64>,
pub(crate) in_event_loop: Cell<bool>,
pub(crate) children: RefCell<Vec<ChildWorker>>,
pub(crate) generation: u64,
}
impl ThreadState {
fn new(
shared: Arc<ThreadShared>,
driver: Box<dyn DriverBackend>,
worker_completion: Option<Arc<WorkerCompletion>>,
generation: u64,
) -> Self {
Self {
driver,
shared,
worker_completion,
local_microtasks: RefCell::new(VecDeque::new()),
local_macrotasks: RefCell::new(VecDeque::new()),
timers: RefCell::new(TimerHeap::new()),
live_intervals: RefCell::new(HashMap::new()),
next_timer_id: Cell::new(1),
tasks: RefCell::new(HashMap::new()),
next_task_id: Cell::new(1),
in_event_loop: Cell::new(false),
children: RefCell::new(Vec::new()),
generation,
}
}
pub(crate) fn handle(&self) -> super::ThreadHandle {
super::ThreadHandle {
shared: Arc::clone(&self.shared),
}
}
pub(crate) fn has_live_children(&self) -> bool {
!self.children.borrow().is_empty()
}
pub(crate) fn has_live_async_operations(&self) -> bool {
self.shared.pending_ops.load(Ordering::Acquire) != 0
}
pub(crate) fn try_begin_shutdown(&self) -> bool {
self.shared
.closing
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
}
pub(crate) struct ThreadShared {
notifier: Box<dyn Notifier>,
pub(crate) remote_macrotasks: RemoteQueue,
pub(crate) pending_ops: AtomicUsize,
pub(crate) closing: AtomicBool,
pub(crate) closed: AtomicBool,
}
impl ThreadShared {
pub(crate) fn new(notifier: Box<dyn Notifier>) -> Self {
Self::with_remote_capacity(notifier, remote_queue_capacity())
}
pub(crate) fn with_remote_capacity(notifier: Box<dyn Notifier>, capacity: usize) -> Self {
Self {
notifier,
remote_macrotasks: RemoteQueue::new(capacity),
pending_ops: AtomicUsize::new(0),
closing: AtomicBool::new(false),
closed: AtomicBool::new(false),
}
}
pub(crate) fn enqueue_macro(&self, task: SendTask) -> Result<(), QueueError> {
self.enqueue(task, true)
}
pub(crate) fn enqueue_internal_wake(&self, task: SendTask) -> Result<(), QueueError> {
self.enqueue(task, false)
}
fn enqueue(&self, task: SendTask, enforce_capacity: bool) -> Result<(), QueueError> {
let mut queue = lock_queue(&self.remote_macrotasks);
if self.closed.load(Ordering::Acquire) {
return Err(QueueError::Closed);
}
if enforce_capacity && queue.len() >= self.remote_macrotasks.capacity {
if !self
.remote_macrotasks
.warned_full
.swap(true, Ordering::AcqRel)
{
tracing::warn!(
target: trace_targets::SCHEDULER,
event = "remote_queue_full",
capacity = self.remote_macrotasks.capacity,
"cross-thread macrotask queue is full; rejecting remote task"
);
}
return Err(QueueError::Full);
}
queue.push_back(task);
drop(queue);
self.notify();
Ok(())
}
pub(crate) fn notify(&self) {
if let Err(error) = self.notifier.notify() {
if error.kind() != io::ErrorKind::BrokenPipe {
tracing::error!(
target: trace_targets::DRIVER,
event = "notify_error",
?error,
"unexpected error sending thread notification"
);
}
}
}
}
pub(crate) struct ChildWorker {
pub(crate) completion: Arc<WorkerCompletion>,
pub(crate) on_exit: Option<LocalTask>,
}
pub(crate) struct WorkerCompletion {
pub(crate) finished: AtomicBool,
pub(crate) parent_event: super::ThreadHandle,
}
pub(crate) fn lock_queue(queue: &RemoteQueue) -> MutexGuard<'_, VecDeque<SendTask>> {
queue.inner.lock().expect("runtime queue poisoned")
}
pub(crate) fn describe_panic(payload: &(dyn std::any::Any + Send)) -> &str {
if let Some(message) = payload.downcast_ref::<&'static str>() {
message
} else if let Some(message) = payload.downcast_ref::<String>() {
message.as_str()
} else {
"Box<dyn Any>"
}
}
fn remote_queue_capacity() -> usize {
*REMOTE_QUEUE_CAPACITY.get_or_init(|| {
std::env::var("RUNITE_REMOTE_QUEUE_CAPACITY")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.filter(|capacity| *capacity >= 1)
.map(|capacity| capacity.min(MAX_REMOTE_QUEUE_CAPACITY))
.unwrap_or(DEFAULT_REMOTE_QUEUE_CAPACITY)
})
}
pub(crate) fn with_current_thread<R: Runtime, T>(f: impl FnOnce(&ThreadState) -> T) -> T {
let ptr = CURRENT_THREAD.with(|cell| {
let mut ptr = cell.get();
if ptr.is_null() {
let (driver, notifier) =
R::create_driver_pair().expect("runtime driver should initialize");
let shared = Arc::new(ThreadShared::new(notifier));
let generation = NEXT_GENERATION.fetch_add(1, Ordering::Relaxed);
let state = Box::new(ThreadState::new(shared, driver, None, generation));
let raw = Box::into_raw(state);
unsafe {
(*raw).driver.bind_current_thread();
}
cell.set(raw);
ptr = raw;
}
ptr
});
unsafe { f(&*ptr) }
}
pub(crate) fn with_installed_thread<T>(f: impl FnOnce(&ThreadState) -> T) -> T {
let ptr = CURRENT_THREAD.with(|cell| cell.get());
assert!(!ptr.is_null(), "runtime state not installed on this thread");
unsafe { f(&*ptr) }
}
pub(crate) fn try_with_installed_thread<T>(f: impl FnOnce(Option<&ThreadState>) -> T) -> T {
let ptr = CURRENT_THREAD.with(|cell| cell.get());
if ptr.is_null() {
f(None)
} else {
unsafe { f(Some(&*ptr)) }
}
}
pub(crate) fn install_thread(
shared: Arc<ThreadShared>,
driver: Box<dyn DriverBackend>,
worker_completion: Option<Arc<WorkerCompletion>>,
) {
CURRENT_THREAD.with(|cell| {
debug_assert!(cell.get().is_null(), "thread runtime already installed");
let generation = NEXT_GENERATION.fetch_add(1, Ordering::Relaxed);
let state = Box::new(ThreadState::new(
shared,
driver,
worker_completion,
generation,
));
let raw = Box::into_raw(state);
unsafe {
(*raw).driver.bind_current_thread();
}
cell.set(raw);
});
}
pub(crate) fn teardown_thread() {
CURRENT_THREAD.with(|cell| {
let ptr = cell.replace(ptr::null_mut());
if !ptr.is_null() {
unsafe {
(*ptr).driver.unbind_current_thread();
drop(Box::from_raw(ptr));
}
}
});
}