refactor(wakes): epoch-stamp every registration-based wake; retire per-primitive wait seqs
The slot epoch is THE wait identity, so the hand-rolled per-primitive copies go away: channel loses cur_wait/next_wait_seq/timed_out, mutex loses Wait.seq and next_seq, TimerTarget::on_timeout takes the epoch. Registrations become (pid, epoch) — channel parked_receiver, mutex waiters, io fd waiters, Blocking io completions, sleep timers, joiner lists — and their wakers move to unpark_at. begin_wait is lock-free, so each primitive opens the wait inside the same critical section that publishes the registration. recv_timeout's wake-classification loop collapses: wakes are precise, so queue → Ok, senders==0 → Disconnected, else Timeout — the 'Defensive' re-register branch is now unreachable by protocol, not by audit. Same for Mutex::lock_timeout's one-shot park. End-state invariant, auditable in one sentence: the only wildcard wake is request_stop, which is terminal.
This commit is contained in:
+19
-16
@@ -18,9 +18,9 @@
|
||||
//! No cancellation. When a non-timer wakeup happens (e.g. lock granted
|
||||
//! before timeout), the timer entry is left in the heap. It will be popped
|
||||
//! eventually and the dispatch will observe "actor is no longer parked /
|
||||
//! wait_seq is stale" and no-op. Cost is ~32 bytes per stale entry plus a
|
||||
//! few cycles on pop; acceptable given the upper bound is "one entry per
|
||||
//! parked actor".
|
||||
//! the wait's epoch was consumed" and no-op. Cost is ~32 bytes per stale
|
||||
//! entry plus a few cycles on pop; acceptable given the upper bound is "one
|
||||
//! entry per parked actor".
|
||||
//!
|
||||
//! Stale pids (slot reused since the timer was inserted) are filtered on
|
||||
//! pop by the scheduler — same convention as the run queue.
|
||||
@@ -35,18 +35,21 @@ use std::time::{Duration, Instant};
|
||||
///
|
||||
/// Held inside `Entry`, dispatched by the scheduler in `pop_due`.
|
||||
pub enum Reason {
|
||||
/// `loom::sleep(d)`. Unpark `pid` unconditionally (modulo the usual
|
||||
/// "still parked?" check the scheduler applies).
|
||||
Sleep,
|
||||
/// A bounded wait — currently only `Mutex::lock_timeout`. On expiry the
|
||||
/// scheduler calls `target.on_timeout(pid, wait_seq)`. The target then
|
||||
/// decides whether `pid` was actually still waiting, and if so unparks
|
||||
/// it with whatever error the wait was bounded for. `wait_seq` lets the
|
||||
/// target tell apart "this wait" from "a later wait by the same actor
|
||||
/// on the same target".
|
||||
/// `sleep(d)`. Wake `pid` via the epoch-matched unpark: if anything
|
||||
/// else (necessarily a terminal wake) already consumed the wait, the
|
||||
/// entry is stale and no-ops at the CAS.
|
||||
Sleep { epoch: u32 },
|
||||
/// A bounded wait (`Mutex::lock_timeout`, `Receiver::recv_timeout`,
|
||||
/// `select_timeout`). On expiry the scheduler calls
|
||||
/// `target.on_timeout(pid, epoch)`. The target then decides whether
|
||||
/// `pid` was actually still waiting (registration still present under
|
||||
/// its lock), and if so takes the registration and unparks via
|
||||
/// `unpark_at`. The epoch is the slot-word park-epoch — the runtime-wide
|
||||
/// wait identity — so a stale entry is doubly inert: the registration
|
||||
/// check misses, and even a racing unpark fails the word's epoch CAS.
|
||||
WaitTimeout {
|
||||
target: Arc<dyn TimerTarget>,
|
||||
wait_seq: u64,
|
||||
epoch: u32,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -55,7 +58,7 @@ pub enum Reason {
|
||||
/// Implementors: do not touch `SchedulerState` other than via the public
|
||||
/// `unpark` / channel APIs. The scheduler is mid-iteration when this fires.
|
||||
pub trait TimerTarget: Send + Sync {
|
||||
fn on_timeout(&self, pid: Pid, wait_seq: u64);
|
||||
fn on_timeout(&self, pid: Pid, epoch: u32);
|
||||
}
|
||||
|
||||
pub struct Entry {
|
||||
@@ -104,8 +107,8 @@ impl Timers {
|
||||
}
|
||||
|
||||
/// Insert a `Sleep` timer. Convenience for the common case.
|
||||
pub fn insert_sleep(&mut self, deadline: Instant, pid: Pid) {
|
||||
self.insert(deadline, pid, Reason::Sleep);
|
||||
pub fn insert_sleep(&mut self, deadline: Instant, pid: Pid, epoch: u32) {
|
||||
self.insert(deadline, pid, Reason::Sleep { epoch });
|
||||
}
|
||||
|
||||
/// Insert an arbitrary timer entry.
|
||||
|
||||
Reference in New Issue
Block a user