RFC 016 Chunk 2a: per-actor timeslice overrun counter
Tally overruns at the slice-expiry site in preempt.rs (the RFC 006 signal), surfaced as ActorInfo.overruns. - Counter is a hot-region AtomicU64 on Slot, single-writer Relaxed load+store (no atomic RMW), read Relaxed by the snapshot (D5). - Reached from the rare expiry branch via a stashed *const Slot in a preempt thread-local, set/cleared on the same resume/return boundary as CURRENT_STOP — one TLS load, no runtime lookup. Shared infra for the messages-received counter next. - Reset in all three slot-lifecycle sites: Slot::vacant, reclaim_slot, install_actor (standing invariant, D7) — per-incarnation counts.
This commit is contained in:
@@ -426,6 +426,14 @@ pub(crate) struct Slot {
|
||||
/// First-resume closure, double-boxed so it fits an `AtomicPtr`
|
||||
/// (`Box<Closure>` is a thin pointer). Swap-to-take; null when absent.
|
||||
closure: AtomicPtr<Closure>,
|
||||
/// RFC 016 Chunk 2 — per-actor timeslice overrun tally. Single-writer: only
|
||||
/// the on-CPU actor's scheduler thread increments it (at the slice-expiry
|
||||
/// site in `preempt.rs`, reached via the stashed slot pointer), so the
|
||||
/// writes are plain Relaxed load+store with no atomic-RMW traffic; the
|
||||
/// snapshot reads it Relaxed from any thread. Lives in the hot region rather
|
||||
/// than `SlotCold` so the increment needs no lock; reset across reuse like
|
||||
/// every other slot field (`vacant` / `reclaim_slot` / `install_actor`).
|
||||
overruns: AtomicU64,
|
||||
/// Cold lifecycle data. See [`SlotCold`].
|
||||
pub(crate) cold: RawMutex<SlotCold>,
|
||||
}
|
||||
@@ -437,6 +445,7 @@ impl Slot {
|
||||
sp: AtomicUsize::new(0),
|
||||
stop_ptr: AtomicPtr::new(std::ptr::null_mut()),
|
||||
closure: AtomicPtr::new(std::ptr::null_mut()),
|
||||
overruns: AtomicU64::new(0),
|
||||
cold: RawMutex::new(SlotCold {
|
||||
actor: None,
|
||||
waiters: Vec::new(),
|
||||
@@ -465,6 +474,31 @@ impl Slot {
|
||||
self.word.load()
|
||||
}
|
||||
|
||||
/// Tally one timeslice overrun (RFC 016 Chunk 2). Single-writer: only the
|
||||
/// on-CPU actor's own thread calls this, at the slice-expiry site, so a
|
||||
/// Relaxed load+store is sufficient and avoids the cache-line lock of an
|
||||
/// atomic RMW.
|
||||
#[inline]
|
||||
pub(crate) fn record_overrun(&self) {
|
||||
let v = self.overruns.load(Ordering::Relaxed);
|
||||
self.overruns.store(v.wrapping_add(1), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Read the overrun tally (Relaxed; the snapshot reads cross-thread).
|
||||
#[inline]
|
||||
pub(crate) fn overruns(&self) -> u64 {
|
||||
self.overruns.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Zero the per-actor introspection counters. Called at every point a slot
|
||||
/// is recycled or freshly occupied (`reclaim_slot`, `install_actor`) so a
|
||||
/// reused slot never carries a previous incarnation's counts — the standing
|
||||
/// slot-lifecycle reset invariant (RFC 016 D7).
|
||||
#[inline]
|
||||
pub(crate) fn reset_counters(&self) {
|
||||
self.overruns.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// A pid's-eye snapshot of the slot. Cold paths re-read this under the
|
||||
/// cold lock (generation can't change while it is held).
|
||||
#[inline]
|
||||
@@ -1003,6 +1037,7 @@ pub(crate) fn install_actor(
|
||||
}
|
||||
slot.sp.store(sp, Ordering::Relaxed);
|
||||
slot.store_closure(closure);
|
||||
slot.reset_counters();
|
||||
inner.live_actors.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
// Publish: only now can pops, unparks, or stops find the actor. The
|
||||
@@ -1043,6 +1078,7 @@ pub(crate) fn reclaim_slot(inner: &RuntimeInner, pid: Pid) {
|
||||
cold.waiters.clear();
|
||||
cold.monitors.clear();
|
||||
cold.links.clear();
|
||||
slot.reset_counters();
|
||||
slot.stop_ptr.store(std::ptr::null_mut(), Ordering::Release);
|
||||
// The generation bump IS the reclaim: every stale pid is dead from
|
||||
// this store onwards (unpark protocol, pops, cold-path re-verifies).
|
||||
@@ -1451,6 +1487,7 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
|
||||
set_actor_sp(sp);
|
||||
set_current_pid(pid);
|
||||
crate::preempt::set_current_stop(stop_flag);
|
||||
crate::preempt::set_current_slot(slot as *const Slot);
|
||||
reset_actor_done();
|
||||
YIELD_INTENT.with(|c| c.set(YieldIntent::Yield));
|
||||
// RFC 005 timeslice inheritance: a slot-popped actor does NOT get a
|
||||
@@ -1474,6 +1511,7 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot_idx: usize) {
|
||||
stats.current_pid_index.store(u32::MAX, Ordering::Relaxed);
|
||||
clear_current_pid();
|
||||
crate::preempt::clear_current_stop();
|
||||
crate::preempt::clear_current_slot();
|
||||
|
||||
let intent = YIELD_INTENT.with(|c| c.get());
|
||||
slot.sp.store(get_actor_sp(), Ordering::Relaxed);
|
||||
|
||||
Reference in New Issue
Block a user