We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 19 of 25
A logical-time reactor: timers without a clock
Add deterministic timers to the supplied single-threaded executor. The executor polls ready tasks; the reactor stores timers and makes their tasks ready when a deadline is reached. Time is an integer here, so the simulation never needs to wait for a real clock.
Your task
Fix the timer ordering in the starter. Keep the given executor and the
public functions, including simulate(delays: Vec<u64>) -> Vec<(usize, u64)>.
The starter already handles registration, clock advancement and reset, but
its heap removes the latest deadline instead of the earliest.
simulate starts at time zero and spawns tasks in input order. Task i
sleeps for delays[i], records (i, now), then repeats that sleep and record
once. Return the log in execution order. Each task contributes two entries;
an empty input returns an empty log.
Process timers in ascending (deadline, task_id) order. When deadlines
match, the lower task ID goes first. A zero-delay sleep completes immediately
without registering a timer. The fixtures use deadlines that fit in u64.
For [3, 1, 2], the result is:
[(1,1), (1,2), (2,2), (0,3), (2,4), (0,6)]
Task 1 records at times 1 and 2, task 2 at times 2 and 4, and task 0 at times 3 and 6. At time 2, task 1 goes before task 2 because its ID is lower.
Order the timer heap
BinaryHeap removes its largest entry first. Wrapping each (deadline, task_id) in std::cmp::Reverse makes the smallest pair come out first.
Update the heap's element type, the entry inserted by Sleep::poll, and
the pattern used when advance removes an entry.
The task ID gives equal deadlines a defined order. This matters to the returned log, even though either task would observe the same logical time.
Drain ready work before advancing time
The supplied driver repeatedly drains the ready queue, then removes one timer and moves the clock to its deadline:
while ex.step() {}
// Once no task is ready, advance to the next timer.
Run the newly awakened task before advancing again. It may register another timer, which must join the heap before the next deadline is chosen. With the starter's max-heap, a later timer can fire first and leave an earlier deadline behind, making the simulated clock move backward.
This ordering is a rule of this simulation, not a rule for every async runtime. A production runtime must also service I/O and timers while other tasks remain runnable.
Register each sleep once
sleep(d) captures now() + d when it is constructed. On each poll, it first
checks whether that deadline has arrived. If so, it returns the current time.
Otherwise, it registers a timer only if armed is false, then returns
Pending. Repeated polls must not add duplicate entries for the same sleep.
Here, Sleep gets the task ID from CURRENT, which Executor::step sets
before polling. It relies on this particular executor instead of saving the
waker from Context. It is not a timer future you can move unchanged into an
arbitrary runtime. General-purpose futures must arrange a wakeup through the
current polling context when they can make progress.
Reset and borrowing
Reset the thread-local reactor at the start of every simulate call. The
harness runs the simulation twice in the same process and grades the second
result. Without a reset, the next run can inherit the previous clock value.
Keep RefCell borrows inside short synchronous operations. The supplied
with_borrow and with_borrow_mut closures release their borrows before the
surrounding future can yield. Holding a borrow across an await can cause a
runtime borrow failure if another task needs the same cell.
This exercise demonstrates timer ordering and the connection between a future, a ready queue and a reactor. It does not implement timer cancellation, operating-system I/O, scheduler fairness or a multithreaded runtime.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.