use std::borrow::Cow;
use std::cell::Cell;
use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering};
use std::time::{Duration, Instant};
use std::{fmt, future::Future, io, panic::Location, ptr, sync::Arc};
use crate::handle::{JoinHandle, SpawnLocation};
use crate::{busy::BusyClock, driver::Notify, metrics::AtomicHistogram};
#[cfg(not(ntex_loom))]
use std::{
sync::atomic::{AtomicBool, AtomicUsize},
thread::yield_now,
};
#[cfg(ntex_loom)]
use loom::{
sync::atomic::{AtomicBool, AtomicUsize},
thread::yield_now,
};
use super::task::{
self, LocalQueue, Remote as RemoteTask, RemoteQueue, Runnable as Task, TaskList, task_ref,
};
use super::{LOG_TARGET, REPORTED, RuntimeBuilder, TaskStats, ThreadId, current_thread_id};
const MAX_SELF_WAKES: u32 = task::MAX_SELF_WAKES;
const REMOTE_BATCH: usize = 128;
const CLOSED: usize = 1 << (usize::BITS - 1);
#[repr(C)]
pub(crate) struct RunnableQueue {
pub(crate) id: ThreadId,
pub(super) handle: Box<dyn Notify>,
pub(super) epoch: Instant,
self_wake_time: Duration,
event_interval: usize,
self_wake_limit: u32,
_remote: [CacheLine; 0],
notified: AtomicBool,
admission: AtomicUsize,
pub(super) remote_queue: RemoteQueue,
_local: [CacheLine; 0],
pub(super) idle: Cell<bool>,
pub(super) poll_start: Cell<Instant>,
pub(super) self_wake_clock: Cell<Option<u32>>,
pub(super) busy: BusyClock,
pub(super) polls: AtomicU64,
pub(super) polls_again: AtomicU64,
pub(super) poll_histogram: AtomicHistogram,
pub(super) self_wakes: AtomicU64,
pub(super) current: AtomicPtr<Location<'static>>,
pub(super) local_queue: LocalQueue,
pub(crate) tasks: TaskList,
}
#[derive(Debug)]
#[repr(align(128))]
pub(super) struct CacheLine;
unsafe impl Send for RunnableQueue {}
unsafe impl Sync for RunnableQueue {}
impl RunnableQueue {
pub(super) fn new(builder: &RuntimeBuilder, handle: Box<dyn Notify>) -> Self {
let now = Instant::now();
Self {
handle,
event_interval: builder.event_interval,
id: current_thread_id(),
idle: Cell::new(true),
poll_start: Cell::new(now),
self_wake_clock: Cell::new(None),
notified: AtomicBool::new(false),
admission: AtomicUsize::new(0),
busy: BusyClock::new(),
polls: AtomicU64::new(0),
polls_again: AtomicU64::new(0),
poll_histogram: AtomicHistogram::default(),
self_wakes: AtomicU64::new(0),
self_wake_limit: builder.self_wake_limit,
self_wake_time: builder.self_wake_time,
epoch: now,
current: AtomicPtr::new(ptr::null_mut()),
local_queue: LocalQueue::new(),
tasks: TaskList::new(),
remote_queue: RemoteQueue::new(),
_remote: [],
_local: [],
}
}
pub(super) unsafe fn spawn<F: Future, const LOCAL: bool>(
self: &Arc<Self>,
future: F,
location: SpawnLocation,
) -> JoinHandle<F::Output> {
JoinHandle::new(unsafe { task::spawn::<_, LOCAL>(future, self, location) })
}
pub(super) unsafe fn spawn_detached<F: Future, const LOCAL: bool>(
self: &Arc<Self>,
future: F,
location: SpawnLocation,
) {
unsafe { task::spawn_detached::<_, LOCAL>(future, self, location) }
}
pub(crate) fn schedule_local(&self, runnable: Task) {
self.local_queue.push(runnable);
if self.idle.get() {
self.start_busy();
if self.current.load(Ordering::Relaxed).is_null() {
self.notify();
}
}
}
pub(super) fn notify(&self) {
#[cold]
fn failed(err: &io::Error) -> ! {
log::error!(target: LOG_TARGET, "Failed to notify runtime driver: {err}; aborting");
std::process::abort();
}
struct AbortOnUnwind;
impl Drop for AbortOnUnwind {
fn drop(&mut self) {
log::error!(target: LOG_TARGET, "Runtime notifier panicked; aborting");
std::process::abort();
}
}
let guard = AbortOnUnwind;
while let Err(err) = self.handle.notify() {
if err.kind() != io::ErrorKind::Interrupted {
failed(&err);
}
}
std::mem::forget(guard);
}
pub(crate) fn admit(self: &Arc<Self>, owned: bool) -> Option<RemotePermit<'_>> {
let mut state = self.admission.load(Ordering::Acquire);
loop {
if state & CLOSED != 0 {
return None;
}
if state == CLOSED - 1 {
std::process::abort();
}
match self.admission.compare_exchange_weak(
state,
state + 1,
Ordering::Acquire,
Ordering::Acquire,
) {
Ok(_) => {
return Some(RemotePermit {
queue: Some(if owned {
Cow::Owned(self.clone())
} else {
Cow::Borrowed(self)
}),
});
}
Err(v) => state = v,
}
}
}
pub(super) fn run(&self) -> bool {
if self.notified.load(Ordering::Relaxed) {
self.notified.swap(false, Ordering::Acquire);
}
for _ in 0..self.event_interval {
if let Some(task) = self.pop_local() {
self.run_task(task);
} else {
break;
}
}
let remote_queue = self
.remote_queue
.drain(self.event_interval, |task| self.run_remote(task));
let more_tasks = !self.local_queue.is_empty() || remote_queue;
if !more_tasks {
self.idle.set(true);
}
more_tasks
}
fn run_remote(&self, task: RemoteTask) {
if let Some(task) = task.into_runnable() {
self.run_task(task);
}
}
fn run_task(&self, task: Task) {
struct Reset<'a>(&'a AtomicPtr<Location<'static>>, *mut Location<'static>);
impl Drop for Reset<'_> {
fn drop(&mut self) {
self.0.store(self.1, Ordering::Relaxed);
}
}
let stats = task_ref(&task);
let prev = self.current.load(Ordering::Relaxed);
self.current.store(
ptr::from_ref(stats.location()).cast_mut(),
Ordering::Relaxed,
);
let wakes = if self.self_wake_limit == 0 {
0
} else {
let wakes = stats.self_wakes();
if wakes != 0 {
stats.set_self_wakes(0);
}
wakes
};
let _reset = Reset(&self.current, prev);
if task.run() {
self.self_wake(stats, wakes);
}
}
pub(super) fn self_wake(&self, task: impl TaskStats, wakes: u32) {
let total = self.self_wakes.load(Ordering::Relaxed);
self.self_wakes
.store(total.wrapping_add(1), Ordering::Relaxed);
if self.self_wake_limit == 0 {
return;
}
let reported = wakes & REPORTED;
let count = ((wakes & !REPORTED) + 1).min(MAX_SELF_WAKES);
let now = self.self_wake_clock.get().unwrap_or_else(|| {
#[allow(clippy::cast_possible_truncation)]
let now = self
.poll_start
.get()
.saturating_duration_since(self.epoch)
.as_millis() as u32;
self.self_wake_clock.set(Some(now));
now
});
if count == 1 {
task.set_streak_start(now);
}
let wakes = count | reported;
if reported == 0 && count >= self.self_wake_limit.min(MAX_SELF_WAKES) {
let elapsed = Duration::from_millis(u64::from(now.wrapping_sub(task.streak_start())));
if elapsed >= self.self_wake_time {
task.set_self_wakes(wakes | REPORTED);
let location = task.location();
log::warn!(
target: LOG_TARGET,
"Task spawned at {location} woke itself {count} times in {elapsed:?} without waiting, possible busy loop"
);
return;
}
}
task.set_self_wakes(wakes);
}
pub(super) fn set_poll_start(&self, now: Instant) {
self.poll_start.set(now);
self.self_wake_clock.set(None);
}
pub(super) fn start_busy(&self) {
let now = Instant::now();
self.idle.set(false);
self.set_poll_start(now);
self.busy.start(now);
}
pub(super) fn add_poll(&self, again: bool) {
let polls = self.polls.load(Ordering::Relaxed);
self.polls.store(polls.wrapping_add(1), Ordering::Relaxed);
if again {
let polls = self.polls_again.load(Ordering::Relaxed);
self.polls_again
.store(polls.wrapping_add(1), Ordering::Relaxed);
}
}
fn clear(&self) {
self.remote_queue.clear();
while let Some(task) = self.pop_local() {
drop(task);
}
}
pub(super) fn shutdown(&self) {
fn cleanup(f: impl Fn(), first_panic: &mut Option<Box<dyn std::any::Any + Send>>) {
while let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(&f)) {
if first_panic.is_none() && !std::thread::panicking() {
*first_panic = Some(payload);
} else {
suppress_cleanup_panic(payload);
}
}
}
let mut first_panic = None;
cleanup(
|| loop {
self.clear();
if !self.tasks.close_first() {
break;
}
},
&mut first_panic,
);
self.admission.fetch_or(CLOSED, Ordering::AcqRel);
while self.admission.load(Ordering::Acquire) != CLOSED {
yield_now();
}
cleanup(|| self.clear(), &mut first_panic);
if let Some(payload) = first_panic {
std::panic::resume_unwind(payload);
}
}
pub(crate) fn is_closed(&self) -> bool {
self.admission.load(Ordering::Acquire) & CLOSED != 0
}
pub(super) fn pop_local(&self) -> Option<Task> {
self.local_queue.pop()
}
}
impl fmt::Debug for RunnableQueue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RunnableQueue")
.field("id", &self.id)
.field("epoch", &self.epoch)
.field("self_wake_time", &self.self_wake_time)
.field("event_interval", &self.event_interval)
.field("self_wake_limit", &self.self_wake_limit)
.field("notified", &self.notified.load(Ordering::Relaxed))
.field("admission", &self.admission.load(Ordering::Relaxed))
.field("polls", &self.polls.load(Ordering::Relaxed))
.field("polls_again", &self.polls_again.load(Ordering::Relaxed))
.field("self_wakes", &self.self_wakes.load(Ordering::Relaxed))
.field("current", &self.current.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
#[cold]
pub(super) fn suppress_cleanup_panic(payload: Box<dyn std::any::Any + Send>) {
fn discard(result: std::thread::Result<()>) {
if let Err(payload) = result {
if payload.is::<&'static str>() || payload.is::<String>() {
drop(payload);
} else {
std::mem::forget(payload);
}
}
}
discard(std::panic::catch_unwind(|| {
log::error!(
target: LOG_TARGET,
"Panic during runtime cleanup suppressed to preserve the original panic"
);
}));
discard(std::panic::catch_unwind(std::panic::AssertUnwindSafe(
|| drop(payload),
)));
}
pub(crate) struct RemotePermit<'a> {
queue: Option<Cow<'a, Arc<RunnableQueue>>>,
}
impl RemotePermit<'_> {
pub(crate) fn schedule(mut self, runnable: RemoteTask) {
self.queue.as_ref().unwrap().remote_queue.push(runnable);
let queue = self.queue.take().unwrap();
queue.admission.fetch_sub(1, Ordering::Release);
if !queue.notified.swap(true, Ordering::Release) {
queue.notify();
}
}
}
impl Drop for RemotePermit<'_> {
fn drop(&mut self) {
if let Some(queue) = &self.queue {
queue.admission.fetch_sub(1, Ordering::Release);
}
}
}
impl RemoteQueue {
fn drain(&self, limit: usize, mut f: impl FnMut(RemoteTask)) -> bool {
for _ in 0..limit + REMOTE_BATCH {
match self.pop() {
Ok(Some(task)) => f(task),
Ok(None) => return false,
Err(_) => return true,
}
}
!self.is_empty()
}
fn clear(&self) {
loop {
match self.pop() {
Ok(Some(task)) => drop(task),
Ok(None) => break,
Err(_) => yield_now(),
}
}
}
}