timer: sleep(duration) via min-heap of (deadline, pid)
Adds a BinaryHeap of timer entries on SchedulerState. sleep() inserts an entry and parks; schedule_loop pops due entries each iteration and unparks them. When the run queue is empty but timers are pending, the OS thread sleeps until the soonest deadline. Single-threaded only; thread::sleep is fine because no other thread can wake us. The IO thread coming next will need a Condvar or pipe wakeup to break this OS-sleep early.
This commit is contained in:
+46
-1
@@ -100,6 +100,8 @@ struct SchedulerState {
|
||||
/// The root supervisor's PID. Children spawned at the top level are
|
||||
/// supervised by this. Set by `run()`.
|
||||
root_pid: Option<Pid>,
|
||||
/// Pending sleep timers. Min-heap keyed by deadline.
|
||||
timers: crate::timer::Timers,
|
||||
}
|
||||
|
||||
impl SchedulerState {
|
||||
@@ -109,6 +111,7 @@ impl SchedulerState {
|
||||
free_list: Vec::new(),
|
||||
run_queue: VecDeque::new(),
|
||||
root_pid: None,
|
||||
timers: crate::timer::Timers::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +334,16 @@ pub fn park_current() {
|
||||
unsafe { crate::context::switch_to_scheduler() };
|
||||
}
|
||||
|
||||
/// Park the current actor for at least `duration`. A zero duration behaves
|
||||
/// like `yield_now` (the deadline is immediately in the past, so the timer
|
||||
/// pops on the next scheduler iteration).
|
||||
pub fn sleep(duration: std::time::Duration) {
|
||||
let me = current_pid().expect("sleep() called outside an actor");
|
||||
let deadline = crate::timer::deadline_from_now(duration);
|
||||
with_sched(|s| s.timers.insert(deadline, me));
|
||||
park_current();
|
||||
}
|
||||
|
||||
/// Wake a parked actor. If the actor isn't parked (already runnable or done)
|
||||
/// this is a no-op — that's important; channel and join can both fire
|
||||
/// spurious unparks under some orderings and we want them to be cheap.
|
||||
@@ -417,9 +430,41 @@ pub const ROOT_PID: Pid = Pid::new(u32::MAX, u32::MAX);
|
||||
|
||||
fn schedule_loop() {
|
||||
loop {
|
||||
// 1. Drain due timers into the run queue.
|
||||
let now = std::time::Instant::now();
|
||||
let due = with_sched(|s| s.timers.pop_due(now));
|
||||
for pid in due {
|
||||
// Same idempotency as `unpark`: only re-queue if still parked.
|
||||
with_sched(|s| {
|
||||
if let Some(slot) = s.slot_mut(pid) {
|
||||
if matches!(slot.state, State::Parked) {
|
||||
slot.state = State::Runnable;
|
||||
s.run_queue.push_back(pid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Pop a runnable actor. If none, sleep on the soonest timer or
|
||||
// exit if there isn't one.
|
||||
let pid = match with_sched(|s| s.run_queue.pop_front()) {
|
||||
Some(p) => p,
|
||||
None => return,
|
||||
None => {
|
||||
let next = with_sched(|s| s.timers.peek_deadline());
|
||||
match next {
|
||||
Some(deadline) => {
|
||||
let now = std::time::Instant::now();
|
||||
if deadline > now {
|
||||
// No other thread can wake us; plain sleep is
|
||||
// correct. When the IO thread lands in v0.2
|
||||
// this becomes a Condvar / pipe wakeup.
|
||||
std::thread::sleep(deadline - now);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
None => return, // no runnables, no timers — done.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Look up sp; skip stale or already-reaped pids.
|
||||
|
||||
Reference in New Issue
Block a user