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:
+33
-8
@@ -96,7 +96,10 @@ impl JoinHandle {
|
||||
Some(cold.outcome.take().expect("Done slot must have outcome"))
|
||||
}
|
||||
crate::slot_state::Status::Live => {
|
||||
cold.waiters.push(me);
|
||||
// begin_wait is lock-free, legal under the cold lock;
|
||||
// registering under it makes the epoch atomic with
|
||||
// the check-Done-or-register linearization point.
|
||||
cold.waiters.push((me, begin_wait()));
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -219,11 +222,30 @@ pub fn park_current() {
|
||||
}
|
||||
|
||||
pub fn unpark(pid: Pid) {
|
||||
// The whole protocol lives on the slot's packed word (gen + state in one
|
||||
// CAS) — see Slot::unpark_action. No runtime lock unless we enqueue.
|
||||
// The whole protocol lives on the slot's packed word (gen + epoch +
|
||||
// state in one CAS) — see slot_state.rs. No runtime lock unless we
|
||||
// enqueue. WILDCARD form: reserved for terminal wakes (request_stop);
|
||||
// every registration-based waker must use `unpark_at`.
|
||||
let _ = try_with_runtime(|inner| inner.unpark(pid));
|
||||
}
|
||||
|
||||
/// Epoch-matched unpark: wake `pid` only if its current wait is still the
|
||||
/// one this waker registered for. The form every registration-based waker
|
||||
/// (channel senders, mutex grants, wait-timers, io completions, joiner
|
||||
/// wakes) must use — see slot_state.rs for the consuming-wake rules.
|
||||
pub(crate) fn unpark_at(pid: Pid, epoch: u32) {
|
||||
let _ = try_with_runtime(|inner| inner.unpark_at(pid, epoch));
|
||||
}
|
||||
|
||||
/// Open a new wait for the CURRENT actor: bump its park-epoch and return
|
||||
/// it. Call once per wait, before registering `(pid, epoch)` with any
|
||||
/// waker. Lock-free (one CAS on the own slot word), so it is legal under
|
||||
/// any lock, including a Channel-class lock.
|
||||
pub(crate) fn begin_wait() -> u32 {
|
||||
let me = current_pid().expect("begin_wait() called outside an actor");
|
||||
with_runtime(|inner| inner.begin_wait(me))
|
||||
}
|
||||
|
||||
/// Request cooperative cancellation of `pid`.
|
||||
///
|
||||
/// Sets the actor's stop flag and wakes it so it observes the flag promptly:
|
||||
@@ -281,8 +303,9 @@ impl Drop for NoPreempt {
|
||||
pub fn sleep(duration: std::time::Duration) {
|
||||
let me = current_pid().expect("sleep() called outside an actor");
|
||||
let _np = NoPreempt::enter();
|
||||
let epoch = begin_wait();
|
||||
let deadline = crate::timer::deadline_from_now(duration);
|
||||
with_runtime(|inner| inner.timers.lock().unwrap().insert_sleep(deadline, me));
|
||||
with_runtime(|inner| inner.timers.lock().unwrap().insert_sleep(deadline, me, epoch));
|
||||
park_current();
|
||||
}
|
||||
|
||||
@@ -290,13 +313,13 @@ pub fn insert_wait_timer(
|
||||
deadline: std::time::Instant,
|
||||
pid: Pid,
|
||||
target: std::sync::Arc<dyn crate::timer::TimerTarget>,
|
||||
wait_seq: u64,
|
||||
epoch: u32,
|
||||
) {
|
||||
with_runtime(|inner| {
|
||||
inner.timers.lock().unwrap().insert(
|
||||
deadline,
|
||||
pid,
|
||||
crate::timer::Reason::WaitTimeout { target, wait_seq },
|
||||
crate::timer::Reason::WaitTimeout { target, epoch },
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -317,9 +340,10 @@ where
|
||||
});
|
||||
{
|
||||
let _np = NoPreempt::enter();
|
||||
let epoch = begin_wait();
|
||||
with_runtime(|inner| {
|
||||
let mut io = inner.io.lock().unwrap();
|
||||
io.as_mut().expect("io thread not started").submit(me, work);
|
||||
io.as_mut().expect("io thread not started").submit(me, epoch, work);
|
||||
});
|
||||
park_current();
|
||||
}
|
||||
@@ -351,9 +375,10 @@ pub fn wait_writable(fd: std::os::fd::RawFd) -> std::io::Result<()> {
|
||||
fn wait_fd(fd: std::os::fd::RawFd, readable: bool, writable: bool) -> std::io::Result<()> {
|
||||
let me = current_pid().expect("wait_*() called outside an actor");
|
||||
let _np = NoPreempt::enter();
|
||||
let epoch = begin_wait();
|
||||
with_runtime(|inner| {
|
||||
let mut io = inner.io.lock().unwrap();
|
||||
io.as_mut().expect("io thread not started").epoll_register(fd, me, readable, writable)
|
||||
io.as_mut().expect("io thread not started").epoll_register(fd, me, epoch, readable, writable)
|
||||
})?;
|
||||
park_current();
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user