fix(runtime): handle timer firing while actor is still Runnable in sleep()
While running benchmarks, a hang surfaced in timer-only workloads — actors sleeping with no IO in flight. Tracing it down, the race lives in sleep(): between the call to `timers.insert_sleep` and the subsequent `park_current` yield, the actor is still in State::Runnable. If the timer fires in that window, the old code's `if matches!(slot.state, State::Parked)` guard silently drops the wakeup. The actor then parks normally and never gets re-queued — it sleeps forever. The fix mirrors what scheduler::unpark() and the IO FdReady path already do: when the timer fires and the slot is still Runnable, set `pending_unpark` instead of re-queuing immediately. The upcoming Park yield sees the flag and re-queues the actor rather than suspending it, closing the race without any new synchronisation. Adds a regression test: 100 actors doing pure timer sleeps across ≥2 scheduler threads. The test asserts both correctness (all actors complete) and timeliness (wall time < 2×sleep duration), which is enough signal to catch a stuck actor even on a single-core CI runner.
This commit is contained in:
+17
-4
@@ -603,10 +603,23 @@ fn schedule_loop(inner: &Arc<RuntimeInner>, slot: usize) {
|
||||
crate::timer::Reason::Sleep => {
|
||||
inner.with_shared(|s| {
|
||||
if let Some(slot) = s.slot_mut(entry.pid) {
|
||||
if matches!(slot.state, State::Parked) {
|
||||
slot.state = State::Runnable;
|
||||
s.run_queue.push_back(entry.pid);
|
||||
crate::te!(crate::trace::Event::Enqueue(entry.pid));
|
||||
match slot.state {
|
||||
State::Parked => {
|
||||
slot.state = State::Runnable;
|
||||
s.run_queue.push_back(entry.pid);
|
||||
crate::te!(crate::trace::Event::Enqueue(entry.pid));
|
||||
}
|
||||
// Actor is between `timers.insert_sleep`
|
||||
// and `park_current` in `sleep()`. Set
|
||||
// the flag so the upcoming Park yield
|
||||
// re-queues instead of suspending.
|
||||
// Mirrors scheduler::unpark() and the
|
||||
// IO FdReady path below.
|
||||
State::Runnable => {
|
||||
slot.pending_unpark = true;
|
||||
crate::te!(crate::trace::Event::UnparkDeferred(entry.pid));
|
||||
}
|
||||
State::Done => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user