use core::cell::{Cell, RefCell};
use core::future::Future;
use core::task::{Poll, Waker};
use std::collections::VecDeque;
use std::io;
use std::sync::Arc;
use std::time::{Duration, Instant};
use super::inbox::Inbox;
use counted::Counted;
use super::backend::Backend;
use super::counters::{self, Counters};
use super::handshake::ParkState;
#[cfg(readiness)]
use super::park::Registrar;
use super::park::{Notifier, Poller};
use super::sources::Sources;
use super::task::{Runnable, ScheduleInfo};
use crate::buf::Pool;
use crate::io::Source;
use crate::sync::atomic_waker::AtomicWaker;
use crate::sync::internal::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use crate::time::TimerOp;
use crate::time::wheel::Wheel;
use spin::Spin;
pub(crate) mod backoff;
mod children;
mod detached;
mod ending;
mod foreign;
mod going;
pub(crate) mod locals;
mod made;
mod order;
#[cfg(port)]
mod port;
mod run;
pub(crate) mod signal;
mod spin;
mod stats;
mod timers;
pub(crate) use children::{watch_exit, watch_exit_elsewhere};
pub(crate) use run::Worked;
#[cfg(test)]
pub(crate) use timers::fire_panic_set;
pub(crate) mod counted;
#[cfg(ring)]
pub(crate) mod recv;
#[cfg(ring)]
pub(crate) mod ring;
pub(crate) const LIFO_MAX: u32 = 3;
pub(crate) const INBOX_INTERVAL: u32 = 31;
pub(crate) const DRIVER_INTERVAL: u32 = 61;
pub(crate) const PUBLISH_INTERVAL: u32 = 8;
pub(crate) struct Shared {
inbox: Inbox,
park: ParkState,
notifier: Notifier,
pub(crate) load: AtomicUsize,
pub(crate) tasks: crate::rt::task::list::TaskList,
detached: AtomicUsize,
stopped: AtomicBool,
draining: AtomicBool,
stop_waker: AtomicWaker,
timer_ops: Counted<TimerOp>,
#[cfg(readiness)]
registrar: Option<Registrar>,
#[cfg(port)]
port: Arc<super::park::iocp::Port>,
io_ops: Counted<IoOp>,
io_closed: AtomicBool,
pub(crate) counters: Counters,
runtime: std::sync::OnceLock<std::sync::Weak<super::runtime::Inner>>,
runtime_addr: AtomicUsize,
writes: AtomicUsize,
released: AtomicBool,
timer_at: AtomicU64,
parks: AtomicU64,
#[cfg(unix)]
pub(crate) signal_pump: std::sync::atomic::AtomicBool,
}
pub(crate) enum IoOp {
Register(Arc<Source>),
Release(u64),
#[cfg(ring)]
RingAttach(u64, crate::io::source::requests::Lease),
#[cfg(ring)]
RingDetach(
u32,
[Option<u64>; 2],
Option<crate::io::source::requests::Lease>,
),
#[cfg(ring)]
RingKick(Arc<Source>),
#[cfg(ring)]
RingCancel(u64),
#[cfg(not(ring))]
Touch(Arc<Source>),
Watch(Arc<crate::process::exit::Exit>),
}
impl IoOp {
fn gone(self) {
match self {
IoOp::Register(source) => source.registration_orphaned(),
IoOp::Watch(exit) => exit.unwatched(),
other => drop(other),
}
}
}
impl Shared {
pub(crate) fn push_remote(&self, runnable: Runnable) {
self.inbox.push(runnable);
if self.stopped.load(Ordering::SeqCst) {
self.drop_stopped(false);
return;
}
self.park.wake(|| self.notifier.notify());
}
pub(crate) fn notify(&self) {
self.notifier.notify();
}
pub(crate) fn unpark(&self) {
self.park.wake(|| self.notifier.notify());
}
pub(crate) fn post_timer(&self, op: TimerOp) {
let wake = op.needs_wake();
self.timer_ops.push(op);
crate::sync::internal::fence(Ordering::SeqCst);
if self.stopped.load(Ordering::SeqCst) {
self.drain_timer_ops();
return;
}
if wake {
self.unpark();
}
}
pub(crate) fn drain_timer_ops(&self) {
crate::sync::internal::fence(Ordering::SeqCst);
self.timer_ops.take_all(TimerOp::orphan);
}
#[cfg(readiness)]
pub(crate) fn registrar(&self) -> Option<&Registrar> {
self.registrar.as_ref()
}
#[cfg(port)]
pub(crate) fn port(&self) -> &Arc<super::park::iocp::Port> {
&self.port
}
#[cfg(ring)]
pub(crate) fn is_ring(&self) -> bool {
self.registrar.is_none()
}
#[cfg(test)]
pub(crate) fn state_line(&self) -> String {
format!(
"parked {}, inbox {}, timer ops {}, io ops {}, load {}, stopped {}, draining {}",
self.park.is_parked(),
if self.inbox.is_empty() {
"empty"
} else {
"not empty"
},
self.timer_ops.len(),
self.io_ops.len(),
self.load.load(Ordering::SeqCst),
self.stopped.load(Ordering::SeqCst),
self.draining.load(Ordering::SeqCst),
)
}
pub(crate) fn post_io(&self, op: IoOp) {
self.io_ops.push(op);
crate::sync::internal::fence(Ordering::SeqCst);
if self.io_closed.load(Ordering::SeqCst) {
self.drain_io_ops();
return;
}
self.unpark();
}
pub(crate) fn close_io(&self) {
self.io_closed.store(true, Ordering::SeqCst);
self.drain_io_ops();
}
fn drain_io_ops(&self) {
crate::sync::internal::fence(Ordering::SeqCst);
self.io_ops.take_all(IoOp::gone);
}
pub(crate) fn release_source(self: &Arc<Self>, token: u64) {
let done = super::context::current(|current| {
current.is_some_and(|shard| shard.is(self) && shard.try_release(token))
});
if !done {
self.post_io(IoOp::Release(token));
}
}
pub(crate) fn is_parked(&self) -> bool {
self.park.is_parked()
}
pub(crate) fn is_stopped(&self) -> bool {
self.stopped.load(Ordering::Acquire)
}
pub(crate) fn set_runtime(&self, runtime: std::sync::Weak<super::runtime::Inner>) {
let addr = runtime.as_ptr() as usize;
if self.runtime.set(runtime).is_ok() {
self.runtime_addr.store(addr, Ordering::Release);
}
}
#[inline]
pub(crate) fn enter_runtime(&self) -> Option<super::context::RuntimeSet> {
let addr = self.runtime_addr.load(Ordering::Acquire);
if addr == 0 || super::context::runtime_is(addr as *const super::runtime::Inner) {
return None;
}
Self::set_runtime_here(self.runtime.get()?)
}
#[inline(never)]
fn set_runtime_here(
runtime: &std::sync::Weak<super::runtime::Inner>,
) -> Option<super::context::RuntimeSet> {
runtime
.upgrade()
.and_then(|inner| super::context::try_set_runtime(&inner))
}
pub(crate) fn write_began(&self) {
self.writes.fetch_add(1, Ordering::SeqCst);
}
pub(crate) fn write_ended(&self) {
if self.writes.fetch_sub(1, Ordering::SeqCst) == 1 && self.stopped.load(Ordering::SeqCst) {
self.nudge_runtime();
}
}
pub(crate) fn settled(&self) -> bool {
self.writes.load(Ordering::SeqCst) == 0
&& (!self.released.load(Ordering::SeqCst) || self.inbox.is_idle())
}
pub(crate) fn release_inbox(&self) {
self.drop_stopped(true);
self.released.store(true, Ordering::SeqCst);
}
fn nudge_runtime(&self) {
if let Some(inner) = self.runtime.get().and_then(std::sync::Weak::upgrade) {
inner.lingering().nudge();
}
}
pub(crate) fn stop(&self) {
self.draining.store(true, Ordering::SeqCst);
self.stopped.store(true, Ordering::SeqCst);
self.stop_waker.wake();
self.unpark();
}
#[inline]
pub(crate) fn takes_tasks(&self) -> bool {
!self.draining.load(Ordering::Relaxed)
}
pub(crate) fn drain(&self) {
self.draining.store(true, Ordering::Relaxed);
crate::sync::internal::fence(Ordering::SeqCst);
if self.nothing_live() {
self.stop_waker.wake();
}
self.unpark();
}
pub(crate) fn last_task_done(&self) {
crate::sync::internal::fence(Ordering::SeqCst);
if self.draining.load(Ordering::Relaxed) {
self.stop_waker.wake();
}
}
fn may_exit(&self) -> bool {
if self.is_stopped() {
return true;
}
if !self.draining.load(Ordering::Acquire) {
return false;
}
crate::sync::internal::fence(Ordering::SeqCst);
self.nothing_live()
}
pub(crate) fn stopped(&self) -> impl Future<Output = ()> + '_ {
core::future::poll_fn(move |cx| {
if self.may_exit() {
return Poll::Ready(());
}
self.stop_waker.register(cx.waker());
if self.may_exit() {
Poll::Ready(())
} else {
Poll::Pending
}
})
}
pub(crate) fn stats(&self) -> crate::stats::ShardStats {
self.counters.read(self.load.load(Ordering::Relaxed))
}
pub(crate) fn idle(&self) -> bool {
self.park.is_parked() && !self.has_remote_work()
}
pub(crate) fn timer_at(&self) -> u64 {
self.timer_at.load(Ordering::SeqCst)
}
pub(crate) fn parks(&self) -> u64 {
self.parks.load(Ordering::SeqCst)
}
pub(crate) fn drop_stopped(&self, mut holding: bool) {
loop {
if !holding && !self.inbox.try_take() {
return;
}
unsafe {
self.inbox.drain(drop);
self.inbox.release();
}
holding = false;
if self.inbox.is_empty() {
break;
}
}
self.nudge_runtime();
}
fn has_remote_work(&self) -> bool {
!self.inbox.is_empty() || self.timer_ops.holds_any() || self.io_ops.holds_any()
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Limits {
pub(crate) max_nesting: usize,
pub(crate) buffer_cap: usize,
pub(crate) backend: Backend,
}
fn reserve_group(pool: &Pool, backend: Backend) -> io::Result<usize> {
#[cfg(ring)]
if let Backend::Ring(cfg) | Backend::RingOrPoll(cfg) = backend {
let bytes = recv::reservation(cfg.recv_buffers, cfg.recv_buffer_size);
if !pool.reserve(bytes) {
return Err(recv::too_large(
cfg.recv_buffers,
cfg.recv_buffer_size,
bytes,
pool.cap(),
));
}
return Ok(bytes);
}
let _ = (pool, backend);
Ok(0)
}
#[cfg(test)]
pub(crate) fn least_buffer_cap() -> usize {
let group = match super::backend::chosen() {
#[cfg(ring)]
Ok(Backend::Ring(cfg) | Backend::RingOrPoll(cfg)) => {
recv::reservation(cfg.recv_buffers, cfg.recv_buffer_size)
}
_ => 0,
};
group + crate::buf::CLASSES[0]
}
#[cfg_attr(
ring,
expect(clippy::large_enum_variant, reason = "one per shard, built with it")
)]
enum Driver {
Poll(RefCell<Poller>),
#[cfg(ring)]
Ring(ring::ShardRing),
#[cfg(ring)]
Gone,
}
pub(crate) struct Shard {
pub(crate) shared: Arc<Shared>,
queue: RefCell<VecDeque<Runnable>>,
lifo: Cell<Option<Runnable>>,
lifo_streak: Cell<u32>,
until_inbox: Cell<u32>,
homes_first: Cell<bool>,
drive_left: Cell<u32>,
drive_at: Cell<u32>,
last_drive: Cell<u64>,
polls: Cell<u64>,
sleeps: Cell<u64>,
slept: Cell<Duration>,
remote_wakes: Cell<u64>,
looks: Cell<u32>,
spin: Spin,
owner: Cell<Option<(u64, usize)>>,
monitor: Option<Arc<super::handoff::Monitor>>,
home: core::cell::OnceCell<Arc<super::handoff::home::Home>>,
limits: Limits,
tuning: super::tuning::Tuning,
driver: Driver,
wheel: RefCell<Wheel>,
precise: RefCell<Wheel>,
epoch: Instant,
fired: RefCell<Vec<Waker>>,
side: RefCell<Vec<foreign::Side>>,
foreign_poll: Cell<bool>,
side_seq: Cell<u64>,
serving: Cell<*const super::runtime::Inner>,
signal: Cell<Option<Arc<signal::Signal>>>,
sources: RefCell<Sources>,
children: RefCell<children::Children>,
pool: Pool,
locals: locals::Locals,
#[cfg(port)]
keeper: RefCell<Option<Arc<port::Keeper>>>,
}
impl Shard {
pub(crate) fn pool(&self) -> &Pool {
&self.pool
}
pub(crate) fn own_home(&self) -> Arc<super::handoff::home::Home> {
Arc::clone(
self.home.get_or_init(|| {
Arc::new(super::handoff::home::Home::new(Arc::clone(&self.shared)))
}),
)
}
pub(crate) fn owns_home(&self, home: &super::handoff::home::Home) -> bool {
self.home
.get()
.is_some_and(|own| core::ptr::eq(Arc::as_ptr(own), home))
}
fn parked_ended(&self) {
if let Some(monitor) = &self.monitor {
monitor.worker_woke();
}
}
pub(crate) fn locals(&self) -> Option<&locals::Locals> {
self.owner.get().is_some().then_some(&self.locals)
}
pub(crate) fn register_source(&self, source: &Arc<Source>) -> io::Result<()> {
#[cfg(ring)]
source.registers_with(self.ring().is_some_and(ring::ShardRing::datagrams_ahead));
let token = match self.sources.borrow_mut().insert(Arc::clone(source)) {
Ok(token) => token,
Err(err) => {
self.shared.counters.refused_registration();
source.registration_abandoned();
return Err(err);
}
};
match source.attach(&self.shared, token) {
Some(Err(err)) => Err(err),
_ => Ok(()),
}
}
fn try_release(&self, token: u64) -> bool {
let Ok(mut sources) = self.sources.try_borrow_mut() else {
return false;
};
let gone = sources.remove(token);
drop(sources);
drop(gone);
true
}
fn apply_io_ops(&self) {
self.shared.io_ops.take_all(|op| {
match op {
IoOp::Register(source) => match self.register_source(&source) {
Ok(()) => {}
Err(err) if crate::io::source::requests::is_refused_for_now(&err) => {
source.look_again();
}
Err(err) => source.registration_failed(&err),
},
IoOp::Release(token) => {
let gone = self.sources.borrow_mut().remove(token);
drop(gone);
}
IoOp::Watch(exit) => self.watch(exit),
#[cfg(ring)]
IoOp::RingAttach(token, lease) => {
let source = self.sources.borrow().get(token).cloned();
if let (Some(ring), Some(source)) = (self.ring(), source) {
source.ring_attached(ring, &self.shared, &lease, token);
}
drop(lease);
}
#[cfg(ring)]
IoOp::RingDetach(slot, out, lease) => {
if let Some(ring) = self.ring() {
ring.detach(slot, out, lease);
}
}
#[cfg(ring)]
IoOp::RingKick(source) => source.ring_kick(self),
#[cfg(not(ring))]
IoOp::Touch(source) => source.touch(self),
#[cfg(ring)]
IoOp::RingCancel(user_data) => {
if let Some(ring) = self.ring() {
ring.cancel(user_data);
}
}
}
});
}
pub(crate) fn shared_addr(&self) -> usize {
Arc::as_ptr(&self.shared) as usize
}
pub(crate) fn is(&self, shared: &Arc<Shared>) -> bool {
Arc::ptr_eq(&self.shared, shared)
}
pub(crate) fn worker_index(&self, runtime: u64) -> Option<usize> {
self.owner
.get()
.and_then(|(owner, index)| (owner == runtime).then_some(index))
}
#[inline(always)]
pub(crate) fn push_local_from(&self, runnable: Runnable, info: ScheduleInfo, in_task: bool) {
if self.tuning.lifo && !info.woken_while_running && in_task {
if let Some(previous) = self.lifo.replace(Some(runnable)) {
self.queue.borrow_mut().push_back(previous);
}
} else {
self.queue.borrow_mut().push_back(runnable);
}
}
#[inline(always)]
pub(crate) fn push_lifo_alone(
&self,
runnable: Runnable,
info: ScheduleInfo,
in_task: bool,
) -> Result<(), Runnable> {
if self.tuning.lifo && !info.woken_while_running && in_task {
match self.lifo.take() {
None => {
self.lifo.set(Some(runnable));
return Ok(());
}
held => self.lifo.set(held),
}
}
Err(runnable)
}
#[inline]
fn has_remote_work(&self) -> bool {
self.has_remote_tasks() || self.has_posted_ops_seqcst()
}
#[inline]
fn has_remote_tasks(&self) -> bool {
let drained = unsafe { self.shared.inbox.is_drained() }
&& super::context::home_is_drained()
&& self.home.get().is_none_or(|home| unsafe { home.is_drained() });
!drained
}
#[inline]
fn has_posted_ops(&self) -> bool {
!self.shared.timer_ops.is_empty() || !self.shared.io_ops.is_empty()
}
#[inline]
fn has_posted_ops_seqcst(&self) -> bool {
!self.shared.timer_ops.is_empty_seqcst() || !self.shared.io_ops.is_empty_seqcst()
}
fn queued(&self) -> usize {
let lifo = self.lifo.take();
let n = self.queue.borrow().len() + usize::from(lifo.is_some());
self.lifo.set(lifo);
n
}
fn has_local_work(&self) -> bool {
unsafe { (*self.lifo.as_ptr()).is_some() || !self.queue.borrow().is_empty() }
}
fn drive(&self, wait: Option<Duration>) -> io::Result<()> {
self.last_drive.set(super::quartz::Quartz::now());
let looks = self.looks.get().wrapping_add(1);
self.looks.set(looks);
let publish = wait != Some(Duration::ZERO) || looks.is_multiple_of(PUBLISH_INTERVAL);
if !self.shared.io_ops.is_empty() {
self.apply_io_ops();
}
self.fire_timers();
let wait = if self.has_local_work() {
Some(Duration::ZERO)
} else {
wait
};
let timeout = match (wait, self.timer_wait(wait.is_none())) {
(Some(w), Some(t)) => Some(w.min(t)),
(w, t) => w.or(t),
};
if publish {
self.publish();
}
self.poll_io(self.tuning.max_io_events, timeout)?;
self.fire_timers();
Ok(())
}
fn poll_io(&self, max: usize, timeout: Option<Duration>) -> io::Result<usize> {
match &self.driver {
#[cfg(readiness)]
Driver::Poll(poller) => {
let sources = self.sources.borrow();
let mut events = 0;
poller
.borrow_mut()
.wait_at_most(max, timeout, |token, readiness| {
events += 1;
if token & crate::rt::sources::CHILD != 0 {
self.exit_came(token);
} else if let Some(source) = sources.get(token) {
source.on_event(token, readiness, self);
}
})?;
Ok(events)
}
#[cfg(port)]
Driver::Poll(poller) => {
let mut events = 0;
poller.borrow_mut().wait_at_most(max, timeout, |entry| {
events += 1;
if entry.lpCompletionKey == crate::rt::park::iocp::EXIT {
self.exit_came(entry.lpOverlapped as usize as u64);
return;
}
unsafe { crate::io::source::port::complete(entry, Some(self)) };
})?;
Ok(events)
}
#[cfg(ring)]
Driver::Ring(ring) => ring.wait(Some(self), timeout).map(|(handled, _)| handled),
#[cfg(ring)]
Driver::Gone => Ok(0),
}
}
pub(crate) fn io_backend(&self) -> crate::IoBackend {
self.driver.backend()
}
fn work_may_come_soon(&self) -> bool {
#[cfg(ring)]
return self
.ring()
.is_some_and(ring::ShardRing::expects_completions);
#[cfg(not(ring))]
false
}
fn blocking_out(&self) -> bool {
self.tuning
.blocking
.as_ref()
.is_some_and(|jobs| jobs.load(Ordering::Relaxed) > 0)
}
#[cfg(ring)]
pub(crate) fn ring(&self) -> Option<&ring::ShardRing> {
match &self.driver {
Driver::Ring(ring) => Some(ring),
Driver::Poll(_) | Driver::Gone => None,
}
}
#[expect(
clippy::panic,
reason = "a failure of the poller itself cannot be handed to any task: it ends the shard loudly"
)]
fn io_ready_now(&self) -> bool {
match self.poll_io(usize::MAX, Some(Duration::ZERO)) {
Ok(events) => events > 0,
Err(err) => panic!("the shard's poller failed: {err}"),
}
}
}
#[cfg(all(test, not(loom)))]
mod tests;
#[cfg(all(test, not(loom)))]
mod tests_foreign;
#[cfg(all(test, not(loom)))]
mod tests_made;
#[cfg(all(test, not(loom)))]
mod tests_pool;
#[cfg(all(test, not(loom)))]
mod tests_signal;