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, 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>,
}
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,
}
}
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 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 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.after / STREAK).max(Duration::from_micros(100));
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();
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;
}
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();
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 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);
}
}