We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 7 of 25
Implementing `Wake`: a real task waker and the lost-wakeup bug
Up to now the driver has been polling in a tight loop, which means a future
could return Pending and forget to say why, and nobody noticed. Real
runtimes do not work like that. They poll a task, and then they wait until
someone tells them that task can make progress. Now you build the “tell them”
half.
pub struct TaskWaker { pub id: usize, pub queue: Arc<Mutex<VecDeque<usize>>> }
pub fn drive<F: Future<Output = u32>>(fut: F) -> Option<u32>
pub fn run(polite: bool, pends: u32) -> Option<u32>
drive seeds a ready queue with task id 0, then loops: pop an id, poll the
future, stop when it is Ready. If the queue drains before the future
finishes, nobody is ever going to poll it again — return None.
You are given two futures that differ in exactly one line. Polite calls
cx.waker().wake_by_ref() before returning Pending. Forgetful does not.
run picks one and drives it. The test set asserts both outcomes:
| future | pends | result |
|---|---|---|
Polite |
3 |
Some(4) |
Forgetful |
0 |
Some(1) — it never pends, so it never needs a wakeup |
Forgetful |
1 |
null |
That null is the entire lesson. In production this bug does not print
anything. It is a program sitting at 0% CPU, not deadlocked in any way a stack
dump can show, simply never polled again. Here it is a value you can assert on.
Wake — a real Waker, no unsafe
std::task::Wake (stable since 1.51) exists so you do not have to touch
RawWakerVTable:
pub trait Wake {
fn wake(self: Arc<Self>);
fn wake_by_ref(self: &Arc<Self>) { /* default: clones the Arc and calls wake */ }
}
Implement it for a type you own, and Waker::from(Arc::new(my_waker)) gives
you a genuine Waker. Overriding wake_by_ref is worth doing: the default
clones the Arc only to drop it again, and your version does not need to.
The bound that will confuse you. Wake itself has no supertraits, but the
conversion does: impl<W: Wake + Send + Sync + 'static> From<Arc<W>> for Waker. A Waker is Send + Sync and may be invoked from any thread, so
everything reachable from it must be too. Put an Rc<RefCell<..>> inside your
waker — which is exactly what the starter does — and you get E0277:
“Rc<RefCell<VecDeque<usize>>> cannot be sent between threads safely”. The
error points at Waker::from, not at your struct, which is why it reads
strangely the first time. Arc<Mutex<..>> is the fix.
The bug hiding in the obvious loop
This is idiomatic-looking and wrong:
while let Some(id) = self.queue.lock().unwrap().pop_front() {
// ... poll the task, which calls wake_by_ref(), which locks the queue ...
}
The MutexGuard is a temporary of the scrutinee, so it stays alive for the
whole loop body. The moment a polled future wakes itself, it tries to lock a
mutex this thread already holds — and std::sync::Mutex is not reentrant. The
fix is to make the pop its own statement so the guard is dropped at the
semicolon:
let id = queue.lock().unwrap().pop_front()?; // guard gone here
Hold locks for the shortest possible region. That advice sounds like folklore
until the first time you write the while let version.
Keep a budget
A future that wakes itself on every poll and never finishes will spin forever.
Real runtimes accept that; a grading harness should not. Bound the loop
(10 000 polls is generous) and return None when it runs out, so a runaway
future fails fast instead of eating the timeout.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.