One I/O Task, Fully Traced
Consider a spawned future awaiting readability from a TCP stream.
1. Spawn preserves the public output type
spawn<F> accepts a concrete Future + Send + 'static and returns
JoinHandle<F::Output>. Internally, Tokio allocates one Cell<F, S> containing
the future, scheduler handle, atomic task state, join waker, and intrusive-list
links.
The scheduler cannot have a separate queue type for every F. RawTask
retains only a pointer to the common header. A per-F, S vtable knows how to
poll, schedule, deallocate, cancel, and read the output.
2. Initial notification enters a run queue
The task starts with NOTIFIED set and references for runtime ownership, the
runnable notification, and the JoinHandle. A spawn from a worker normally
uses local scheduling; a spawn from elsewhere enters the global injection
queue and unparks a worker.
3. A worker polls the future
The worker selects local work, periodically checks global work, and otherwise
steals from peers. Before polling, the task state atomically moves from idle to
RUNNING, preventing concurrent poll or cancellation-drop access.
The future calls the TCP read implementation. No bytes are ready, so the
resource’s ScheduledIo stores the task waker for readable interest and the
future returns Poll::Pending. The task transitions back to idle.
4. The worker does something else
It polls another runnable task. If no work remains, a worker parks through the combined time/I/O driver instead of spinning.
5. Mio reports readiness
The OS poller returns an event. Tokio converts it to its readiness flags,
updates the ScheduledIo atomic state, removes matching stored wakers under a
lock, releases the lock, and wakes them.
6. Wake means schedule, not execute immediately
The raw waker atomically sets NOTIFIED. If the task was idle and not already
queued, its scheduler submits one runnable reference and unparks a worker.
Repeated wake-ups coalesce while NOTIFIED is already set.
7. The next poll makes progress
A worker polls the future again. The read syscall now succeeds. If the outer
future completes, its output replaces the future in the task stage, state moves
to COMPLETE, and the waker registered by JoinHandle is invoked.
The awaiting caller later takes F::Output through the typed join handle. The
scheduler queue never needed to know that output type.