We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 18 of 25
Spawning, `JoinHandle`, and getting values back out
Your executor can run tasks. It cannot yet tell you what any of them
produced — spawn takes a Future<Output = ()> and the value goes nowhere.
Fixing that is what turns a loop into a runtime, because it requires one task
to wake another.
pub struct Shared<T> { value: Option<T>, waker: Option<Waker> }
pub struct JoinHandle<T> { /* an Rc to a Shared<T> */ }
impl<T> Future for JoinHandle<T> { type Output = T; .. }
impl Executor {
pub fn spawn<T: 'static>(&mut self, fut: impl Future<Output = T> + 'static) -> JoinHandle<T>;
}
fan_in is given: it spawns one worker per input (each yielding a different
number of times, so they finish out of order), then spawns a collector task
that awaits every handle in order and pushes the results. The output is
always the inputs doubled, in input order.
The mechanism
A spawned task and its handle share one cell:
-
spawnallocates the shared cell, wraps the caller’s future in a smallasync moveblock that awaits it and writes the result into the cell, hands the wrapper tospawn_raw, and returns aJoinHandleholding the other end. -
JoinHandle::polllooks in the cell. Value there?Ready. Nothing there? Store the current waker and returnPending. - Task completion writes the value, takes the stored waker, and calls it.
Notice what that last step is: task A waking task B. That is cross-task
wakeup, and it is the thing your executor could not do before. Everything a
runtime offers — channels, mutexes, timers, JoinHandle — is this same
pattern: a shared cell, a parked waker, and a wake on completion.
The contract clause that finally bites
Item 16.6 stated three clauses, and the second one has been theory until now:
On repeated polls, only the waker from the most recent
Contextshould be scheduled. Wakers are not cumulative.
The starter caches the first waker it ever sees and ignores later ones. That
looks harmless — surely the same task keeps polling the same handle? It is
not, and fan_in proves it: before the collector exists, every handle is
polled once with a throwaway Waker::noop(). A handle that remembers only its
first waker remembers that one forever. When the worker finishes it wakes a
no-op, the collector is never re-queued, the ready queue drains, run returns,
and the output is empty.
The peek is contrived-looking and the situation it stands for is not. A future
polled once by one executor and then moved to another, a future polled inside
a select! arm and then awaited directly, a task migrated between worker
threads by a work-stealing scheduler — all of them hand the same future a
different Waker than last time. Overwriting unconditionally is the only
correct policy. (Waker::will_wake exists as a cheap “is this the same waker?”
check to skip a clone, but it is explicitly best-effort and may answer false
for two wakers that would in fact wake the same task, so it can only ever be an
optimisation.)
Order of operations at completion
Write the value first, then take the waker, then release the borrow, and only then wake:
let waker = {
let mut shared = cell.borrow_mut();
shared.value = Some(value);
shared.waker.take()
};
if let Some(waker) = waker { waker.wake(); }
Waking while still holding the borrow invites re-entrancy: the woken task may
run, or may try to touch the same cell, and now you own a RefCell that is
already mutably borrowed (panic) or a Mutex you already hold (deadlock).
This shape — compute under the lock, act outside it — is worth making a habit.
A nice thing to notice
The collector awaits its handles sequentially, and the workers still
interleave. Spawning is eager-ish: the moment you spawn, the task is on the
ready queue and will be polled whether or not anybody is awaiting its handle.
A plain future is the opposite — completely inert until polled. Both models
exist, they behave differently, and dropping a JoinHandle in a real runtime
does not cancel the task for exactly this reason.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.