use core::pin::pin;
use std::sync::{Arc, OnceLock, Weak};
use std::thread::Thread;
use std::time::Duration;
use kovan_queue::seg_queue::SegQueue;
use super::bell::Bell;
use super::life::Life;
use super::slot::{self, Slot};
use crate::rt::runtime::Inner;
use crate::rt::shard::{Shard, Shared};
use crate::sync::internal::{AtomicBool, AtomicPtr, AtomicUsize, Ordering, fence};
const STREAK: u32 = 4;
pub(crate) struct Owned(pub(crate) Arc<Shard>);
unsafe impl Send for Owned {}
struct Held(Weak<Shard>);
unsafe impl Send for Held {}
unsafe impl Sync for Held {}
#[derive(Clone)]
pub(crate) struct Runner {
pub(crate) slot: Arc<Slot>,
pub(crate) life: Arc<Life>,
}
struct Place {
shared: Arc<Shared>,
shard: Held,
first: Runner,
}
pub(crate) struct Gift {
pub(crate) place: usize,
pub(crate) shard: Owned,
pub(crate) slot: Arc<Slot>,
}
pub(crate) struct Spare {
thread: Thread,
life: Arc<Life>,
gift: AtomicPtr<Gift>,
}
impl Spare {
pub(crate) fn here(life: Arc<Life>) -> Arc<Self> {
Arc::new(Self {
thread: std::thread::current(),
life,
gift: AtomicPtr::new(core::ptr::null_mut()),
})
}
fn take_gift(&self) -> Option<Gift> {
let gift = self.gift.swap(core::ptr::null_mut(), Ordering::Acquire);
(!gift.is_null()).then(|| *unsafe { Box::from_raw(gift) })
}
}
impl Drop for Spare {
fn drop(&mut self) {
drop(self.take_gift());
}
}
pub(crate) struct Monitor {
after: Duration,
full_fence: bool,
places: Box<[OnceLock<Place>]>,
spares: SegQueue<Arc<Spare>>,
asleep: AtomicBool,
bell: Bell,
closed: AtomicBool,
closing: crate::sync::Notify,
cores: Vec<usize>,
long_poll: Option<crate::rt::tuning::TaskHook>,
detached: AtomicUsize,
}
impl Monitor {
pub(crate) fn new(
after: Duration,
places: usize,
cores: Vec<usize>,
long_poll: Option<crate::rt::tuning::TaskHook>,
) -> Self {
Self {
after,
full_fence: !super::fence::available(),
places: (0..places).map(|_| OnceLock::new()).collect(),
spares: SegQueue::new(),
asleep: AtomicBool::new(false),
bell: Bell::new(),
closed: AtomicBool::new(false),
closing: crate::sync::Notify::new(),
cores,
long_poll,
detached: AtomicUsize::new(0),
}
}
pub(crate) fn detached(&self) -> usize {
self.detached.load(Ordering::SeqCst)
}
pub(crate) fn count_out(&self) {
self.detached.fetch_sub(1, Ordering::SeqCst);
}
pub(crate) fn count_in(&self) {
self.detached.fetch_add(1, Ordering::SeqCst);
}
fn report_long(&self, task: u64) {
let (Some(hook), Some(id)) = (
&self.long_poll,
core::num::NonZeroU64::new(task).map(crate::rt::task::Id),
) else {
return;
};
let meta = crate::rt::tuning::TaskMeta::new(id);
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| hook(&meta)));
}
pub(crate) fn slot(&self) -> Arc<Slot> {
Arc::new(Slot::new(self.full_fence))
}
pub(crate) fn started(&self, index: usize, shard: &Arc<Shard>, first: Runner) {
if let Some(place) = self.places.get(index) {
let _ = place.set(Place {
shared: Arc::clone(&shard.shared),
shard: Held(Arc::downgrade(shard)),
first,
});
}
self.worker_woke();
}
pub(crate) fn worker_woke(&self) {
fence(Ordering::SeqCst);
if self.asleep.load(Ordering::SeqCst) {
self.bell.ring();
}
}
pub(crate) fn close(&self) {
self.closed.store(true, Ordering::SeqCst);
self.closing.notify_waiters();
self.bell.ring();
while let Ok(spare) = self.spares.try_pop() {
spare.thread.unpark();
}
}
pub(crate) fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
pub(crate) fn closing(&self) -> crate::sync::Notified<'_> {
self.closing.notified()
}
pub(crate) fn tick(&self) -> Duration {
(self.after / STREAK).max(Duration::from_micros(100))
}
#[cfg(test)]
pub(crate) fn first_life(&self, index: usize) -> Option<Arc<Life>> {
Some(Arc::clone(&self.places.get(index)?.get()?.first.life))
}
pub(crate) fn wait_for_shard(&self, me: &Arc<Spare>) -> Option<Gift> {
self.spares.push(Arc::clone(me));
loop {
if let Some(gift) = me.take_gift() {
return Some(gift);
}
if self.is_closed() {
return me.take_gift();
}
std::thread::park();
}
}
pub(crate) async fn home_done(&self, home: &super::home::Home) {
let mut closing = pin!(self.closing.notified());
closing.as_mut().enable();
if self.is_closed() {
return;
}
core::future::poll_fn(|cx| {
if unsafe { home.poll_drained(cx) }.is_ready() {
return core::task::Poll::Ready(());
}
closing.as_mut().poll(cx)
})
.await;
}
fn all_parked(&self) -> bool {
self.places
.iter()
.filter_map(OnceLock::get)
.all(|place| place.shared.is_parked() || place.shared.is_stopped())
}
}
struct Watch {
runner: Runner,
last: u64,
streak: u32,
longest: u32,
reported: u64,
}
pub(crate) fn run(monitor: &Arc<Monitor>, runtime: &Weak<Inner>) {
monitor.bell.bind();
let tick = monitor.tick();
let mut watches: Vec<Option<Watch>> = (0..monitor.places.len()).map(|_| None).collect();
let mut waiting: Option<Gift> = None;
let deliver = |watches: &mut Vec<Option<Watch>>, gift: Gift| match give(monitor, runtime, gift)
{
Ok((place, life)) => {
if let Some(watch) = watches[place].as_mut() {
watch.runner.life = life;
}
None
}
Err(gift) => Some(gift),
};
while !monitor.is_closed() {
if let Some(gift) = waiting.take() {
waiting = deliver(&mut watches, gift);
}
if waiting.is_none() && monitor.all_parked() {
monitor.asleep.store(true, Ordering::SeqCst);
fence(Ordering::SeqCst);
if monitor.all_parked() && !monitor.is_closed() {
monitor.bell.wait();
}
monitor.asleep.store(false, Ordering::SeqCst);
watches
.iter_mut()
.flatten()
.for_each(|watch| watch.streak = 0);
continue;
}
let next = std::time::Instant::now() + tick;
while let Some(left) = next.checked_duration_since(std::time::Instant::now())
&& !left.is_zero()
&& !monitor.is_closed()
{
monitor.bell.wait_for(left);
}
for (index, place) in monitor.places.iter().enumerate() {
let Some(place) = place.get() else {
continue;
};
let watch = watches[index].get_or_insert_with(|| Watch {
runner: place.first.clone(),
last: slot::inside(0),
streak: 0,
longest: 0,
reported: slot::inside(0),
});
let word = watch.runner.slot.word();
if slot::is_outside(word) && word == watch.last {
watch.streak += 1;
} else {
if watch.streak > watch.longest {
watch.longest = watch.streak;
place.shared.counters.longest_poll(tick * watch.streak);
}
watch.streak = 0;
}
watch.last = word;
if watch.streak < STREAK {
continue;
}
let long = (watch.reported != word).then(|| {
watch.reported = word;
let task = watch.runner.slot.task();
if watch.runner.slot.word() == word {
task
} else {
0
}
});
if waiting.is_none() {
watch.streak = 0;
if let Some(gift) = take(monitor, runtime, place, index, watch, word) {
place.shared.counters.longest_poll(tick * STREAK);
waiting = deliver(&mut watches, gift);
}
}
if let Some(task) = long {
monitor.report_long(task);
}
}
}
if let Some(gift) = waiting.take() {
let_go(runtime, gift);
}
}
fn let_go<T>(runtime: &Weak<Inner>, held: T) {
let inner = runtime.upgrade();
let _runtime = inner.as_ref().map(crate::rt::context::set_runtime);
drop(held);
}
fn take(
monitor: &Monitor,
runtime: &Weak<Inner>,
place: &Place,
index: usize,
watch: &mut Watch,
word: u64,
) -> Option<Gift> {
let shard = place.shard.0.upgrade()?;
let old = &watch.runner;
old.slot.claim();
if monitor.full_fence {
fence(Ordering::SeqCst);
} else if !super::fence::heavy() {
old.slot.cancel();
place.shared.counters.handoff_failed();
let_go(runtime, shard);
return None;
}
if old.slot.word() != word || old.slot.is_pinned() {
old.slot.cancel();
let_go(runtime, shard);
return None;
}
if !old.slot.take() {
let_go(runtime, shard);
return None;
}
monitor.count_in();
old.life.detach();
old.life.unpin(&monitor.cores);
shard.hand_over_queues();
shard.shared.counters.handed_off();
let slot = monitor.slot();
watch.runner = Runner {
slot: Arc::clone(&slot),
life: Arc::clone(&old.life),
};
watch.last = slot::inside(0);
watch.reported = slot::inside(0);
Some(Gift {
place: index,
shard: Owned(shard),
slot,
})
}
fn give(monitor: &Monitor, runtime: &Weak<Inner>, gift: Gift) -> Result<(usize, Arc<Life>), Gift> {
let place = gift.place;
if let Ok(spare) = monitor.spares.try_pop() {
let life = Arc::clone(&spare.life);
spare
.gift
.store(Box::into_raw(Box::new(gift)), Ordering::Release);
spare.thread.unpark();
let_go(runtime, spare);
return Ok((place, life));
}
let Some(inner) = runtime.upgrade() else {
return Err(gift);
};
let life = Arc::new(Life::new());
crate::rt::workers::Workers::spawn_runner(&inner, gift, Arc::clone(&life))?;
Ok((place, life))
}
#[cfg(all(test, not(loom)))]
mod tests {
use super::*;
use crate::rt::shard::Limits;
#[expect(
clippy::arc_with_non_send_sync,
reason = "a worker's shard is shared by count, as `Workers` makes it"
)]
fn worker_shard() -> Arc<Shard> {
Arc::new(
Shard::new(Limits {
max_nesting: 8,
buffer_cap: crate::buf::DEFAULT_CAP,
backend: crate::rt::backend::chosen().unwrap(),
})
.unwrap(),
)
}
fn watched(shard: &Arc<Shard>) -> (Monitor, Arc<Slot>, Watch) {
let monitor = Monitor::new(Duration::from_millis(10), 1, Vec::new(), None);
let slot = monitor.slot();
let first = Runner {
slot: Arc::clone(&slot),
life: Arc::new(Life::new()),
};
monitor.started(0, shard, first.clone());
slot.go_out(1);
let watch = Watch {
runner: first,
last: slot::outside(1),
streak: STREAK,
longest: 0,
reported: slot::inside(0),
};
(monitor, slot, watch)
}
fn take_outside(monitor: &Monitor, watch: &mut Watch) -> Option<Gift> {
let place = monitor.places[0].get().unwrap();
take(monitor, &Weak::new(), place, 0, watch, slot::outside(1))
}
#[test]
fn a_take_claims_nothing_of_a_shard_no_one_holds() {
let shard = worker_shard();
let (monitor, slot, mut watch) = watched(&shard);
drop(shard);
assert!(take_outside(&monitor, &mut watch).is_none());
assert_eq!(slot.come_back(1), slot::Back::In);
}
#[test]
fn a_worker_backing_out_of_its_park_rings_the_monitor_asleep() {
let monitor = Arc::new(Monitor::new(Duration::from_millis(10), 1, Vec::new(), None));
let shard = Shard::worker(
Limits {
max_nesting: 8,
buffer_cap: crate::buf::DEFAULT_CAP,
backend: crate::rt::backend::chosen().unwrap(),
},
crate::rt::tuning::Tuning::default(),
1,
0,
Some(Arc::clone(&monitor)),
)
.unwrap();
monitor.asleep.store(false, Ordering::SeqCst);
assert!(
!shard.begin_park(|| true),
"work came: the worker backs out"
);
assert_eq!(monitor.bell.rings(), 0, "a monitor awake was rung");
monitor.asleep.store(true, Ordering::SeqCst);
assert!(
!shard.begin_park(|| true),
"work came: the worker backs out"
);
assert_eq!(monitor.bell.rings(), 1, "the monitor asleep was not rung");
}
#[test]
fn a_take_lets_a_call_under_way_be_and_takes_the_poll_once_it_ends() {
let shard = worker_shard();
let (monitor, slot, mut watch) = watched(&shard);
let (word, back) = slot.mark();
assert_eq!(back, slot::Back::In);
assert_eq!(word, slot::outside(1), "the call read the poll's word");
assert!(
take_outside(&monitor, &mut watch).is_none(),
"the shard was taken while a call was in it"
);
slot.unmark(word);
assert_eq!(slot.word(), slot::outside(1), "the poll is outside again");
let gift = take_outside(&monitor, &mut watch).expect("the poll, outside again, is taken");
assert_eq!(
slot.mark().1,
slot::Back::Taken,
"a call after the take went into the shard"
);
drop(gift);
}
#[test]
fn a_call_that_meets_a_pending_claim_cancels_it_and_the_take_fails() {
let shard = worker_shard();
let (_monitor, slot, _watch) = watched(&shard);
slot.claim();
let (word, back) = slot.mark();
assert_eq!(back, slot::Back::In);
assert!(
!slot.take(),
"the take won over a call that cancelled first"
);
slot.unmark(word);
assert_eq!(slot.come_back(1), slot::Back::In);
}
#[test]
fn a_call_after_the_take_finds_no_shard_and_the_poll_ends_detached() {
let shard = worker_shard();
let monitor = Monitor::new(Duration::from_millis(10), 1, Vec::new(), None);
let slot = monitor.slot();
let first = Runner {
slot: Arc::clone(&slot),
life: Arc::new(Life::new()),
};
monitor.started(0, &shard, first.clone());
let _entered = crate::rt::context::enter_watched(&shard, Some(&slot));
let (found, polled) = crate::rt::context::poll(&shard.shared, || {
let before = crate::rt::context::current(|current| current.is_some());
let word = slot.word();
assert!(slot::is_outside(word), "the poll is outside its shard");
let mut watch = Watch {
runner: first.clone(),
last: word,
streak: STREAK,
longest: 0,
reported: slot::inside(0),
};
let place = monitor.places[0].get().unwrap();
let gift = take(&monitor, &Weak::new(), place, 0, &mut watch, word)
.expect("the blocked poll was taken");
let after = crate::rt::context::current(|current| current.is_some());
drop(gift);
(before, after)
});
assert_eq!(
found,
(true, false),
"a call after the take found the shard"
);
assert_eq!(polled, crate::rt::context::Polled::Detached);
}
#[test]
fn an_owner_step_mark_holds_off_a_take_until_its_settle() {
use crate::rt::context::{self, Marked};
let shard = worker_shard();
let monitor = Monitor::new(Duration::from_millis(10), 1, Vec::new(), None);
let slot = monitor.slot();
let first = Runner {
slot: Arc::clone(&slot),
life: Arc::new(Life::new()),
};
monitor.started(0, &shard, first.clone());
let _entered = context::enter_watched(&shard, Some(&slot));
let (_, polled) = context::poll(&shard.shared, || {
let word = slot.word();
assert!(matches!(context::mark_call(), Marked::In));
assert!(
!slot::is_outside(slot.word()),
"the step's call is not marked"
);
assert!(
matches!(context::mark_call(), Marked::Not),
"a call in the step marked"
);
let mut watch = Watch {
runner: first.clone(),
last: word,
streak: STREAK,
longest: 0,
reported: slot::inside(0),
};
let place = monitor.places[0].get().unwrap();
assert!(
take(&monitor, &Weak::new(), place, 0, &mut watch, word).is_none(),
"the shard was taken while a step was marked"
);
context::unmark_call();
assert_eq!(slot.word(), word, "the step's end left the poll inside");
let gift = take(&monitor, &Weak::new(), place, 0, &mut watch, word)
.expect("the poll, outside again, was not taken");
assert!(matches!(context::mark_call(), Marked::Taken));
drop(gift);
});
assert_eq!(polled, context::Polled::Detached);
}
#[cfg(unix)]
#[test]
fn an_owners_full_send_whose_shard_is_taken_before_the_rest_of_its_poll_asks_again() {
use std::io::Read;
use std::os::fd::AsRawFd;
use std::os::unix::net::UnixStream;
use std::task::{Context, Poll};
use crate::rt::context;
if !super::super::fence::available() {
eprintln!("skipping: no heavy fence here, so every side starts shared");
return;
}
let shard = worker_shard();
#[cfg(ring)]
if shard.shared.is_ring() {
eprintln!("skipping: a ring's side never waits for a write edge");
return;
}
let monitor = Arc::new(Monitor::new(Duration::from_millis(10), 1, Vec::new(), None));
let slot = monitor.slot();
let first = Runner {
slot: Arc::clone(&slot),
life: Arc::new(Life::new()),
};
monitor.started(0, &shard, first.clone());
let (a, mut b) = UnixStream::pair().unwrap();
b.set_nonblocking(true).unwrap();
let handle = crate::io::Handle::new(a).unwrap();
let src = Arc::clone(handle.source());
let fd = handle.get_ref().as_raw_fd();
let _entered = context::enter_watched(&shard, Some(&slot));
let mut cx = Context::from_waker(std::task::Waker::noop());
let mut nothing = [0u8; 1];
let (read, _) = context::poll(&shard.shared, || {
src.poll_read(&mut cx, &mut nothing, false)
});
assert!(read.is_pending(), "{read:?}");
let (first_write, _) = context::poll(&shard.shared, || src.poll_write(&mut cx, b"x"));
assert!(matches!(first_write, Poll::Ready(Ok(1))), "{first_write:?}");
let mut one = [0u8; 1];
let deadline = std::time::Instant::now() + Duration::from_secs(10);
while b.read(&mut one).is_err() {
assert!(
std::time::Instant::now() < deadline,
"the first byte never came"
);
std::thread::yield_now();
}
let chunk = [0u8; 4096];
while unsafe { libc::send(fd, chunk.as_ptr().cast(), chunk.len(), libc::MSG_DONTWAIT) } > 0
{
}
let gift = std::rc::Rc::new(core::cell::Cell::new(None));
let (keep, peer, turn_on, taker, old) = (
std::rc::Rc::clone(&gift),
b.try_clone().unwrap(),
Arc::clone(&shard),
Arc::clone(&monitor),
Arc::clone(&slot),
);
crate::io::source::write::AFTER_FULL_SEND.set(Some(Box::new(move || {
let word = old.word();
let mut watch = Watch {
runner: first.clone(),
last: word,
streak: STREAK,
longest: 0,
reported: slot::inside(0),
};
let place = taker.places[0].get().unwrap();
keep.set(take(&taker, &Weak::new(), place, 0, &mut watch, word));
let mut sink = [0u8; 4096];
while (&peer).read(&mut sink).is_ok_and(|n| n > 0) {}
let _ = turn_on.turn(64, Some(Duration::ZERO));
})));
let (second, polled) = context::poll(&shard.shared, || src.poll_write(&mut cx, b"y"));
assert!(gift.take().is_some(), "the shard was not taken in the poll");
assert_eq!(polled, context::Polled::Detached);
assert!(matches!(second, Poll::Ready(Ok(1))), "{second:?}");
assert!(
matches!(b.read(&mut one), Ok(1)) && one == *b"y",
"the byte waits behind a side blocked on an edge its driver passed over"
);
}
#[test]
fn a_gift_to_a_spare_that_left_goes_inside_its_runtime() {
use std::sync::atomic::AtomicU64;
static DROPPED_IN: AtomicU64 = AtomicU64::new(0);
struct Marks;
impl Drop for Marks {
fn drop(&mut self) {
let id = crate::rt::context::runtime().map_or(u64::MAX, |inner| inner.id());
DROPPED_IN.store(id, Ordering::SeqCst);
}
}
static LOCAL: crate::runtime::ShardLocal<Marks> =
crate::runtime::ShardLocal::__new(|| Marks);
let rt = crate::runtime::Builder::new()
.worker_threads(0)
.build()
.unwrap();
let inner = Arc::clone(rt.inner());
let monitor = Arc::new(Monitor::new(Duration::from_millis(10), 1, Vec::new(), None));
let shard = Shard::worker(
Limits {
max_nesting: 8,
buffer_cap: crate::buf::DEFAULT_CAP,
backend: crate::rt::backend::chosen().unwrap(),
},
crate::rt::tuning::Tuning::default(),
inner.id(),
0,
Some(Arc::clone(&monitor)),
)
.unwrap();
{
let _in = crate::rt::context::enter(&shard);
LOCAL.with(|_| ());
}
monitor.close();
let waits = Arc::clone(&monitor);
std::thread::spawn(move || {
assert!(
waits
.wait_for_shard(&Spare::here(Arc::new(Life::new())))
.is_none()
);
})
.join()
.unwrap();
let gift = Gift {
place: 0,
shard: Owned(shard),
slot: monitor.slot(),
};
assert!(give(&monitor, &Arc::downgrade(&inner), gift).is_ok());
assert_eq!(
DROPPED_IN.load(Ordering::SeqCst),
inner.id(),
"the shard went outside its runtime"
);
}
#[test]
fn a_taken_shard_outlives_its_runner_letting_go() {
let shard = worker_shard();
let (monitor, slot, mut watch) = watched(&shard);
let gift = take_outside(&monitor, &mut watch).expect("the runner was outside");
assert_eq!(slot.come_back(1), slot::Back::Taken);
drop(shard);
assert_eq!(Arc::strong_count(&gift.shard.0), 1);
}
#[test]
fn a_take_whose_heavy_fence_fails_cancels_its_claim_and_is_counted() {
let shard = worker_shard();
let (mut monitor, slot, mut watch) = watched(&shard);
monitor.full_fence = false;
super::super::fence::inject_failure(true);
let gift = take_outside(&monitor, &mut watch);
super::super::fence::inject_failure(false);
assert!(gift.is_none(), "a take relied on a failed fence");
assert_eq!(slot.come_back(1), slot::Back::In, "the claim was left up");
let stats = shard.shared.counters.read(0);
assert_eq!(stats.handoffs_failed, 1, "the failed take was not counted");
assert_eq!(stats.handoffs, 0);
slot.go_out(1);
let gift = take_outside(&monitor, &mut watch).expect("the poll was not taken");
let stats = shard.shared.counters.read(0);
assert_eq!((stats.handoffs, stats.handoffs_failed), (1, 1));
drop(gift);
}
}