tech-sharing / 2026-04-15
From Future::poll to tokio's work-stealing, explained in one go.
agenda.toml
// context
One OS thread per connection, 2–8MB stack each. 100k connections = hundreds of GB of RAM.
❌ Unrealisticepoll/kqueue plus callback hell. Fast, but painful to write and bug-prone.
😩 callback hellReads like synchronous code, compiles to a state machine. One thread runs thousands of tasks.
✅ Rust's choicedeep-dive · 1 / 2
The compiler turns an async fn into an anonymous state machine that implements the Future trait. The runtime does one thing: poll it repeatedly until it returns Ready.
pub trait Future { type Output; fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Self::Output>; } // Poll::Pending → suspended, waits for the waker // Poll::Ready(v) → done, yields v
deep-dive · 2 / 2
Multi-thread runtime = N workers, each with its own local queue. An idle worker "steals" tasks from other queues.
mini-runtime.rs · ~40 LOC
use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll, Wake, Waker}; struct Task(Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>); impl Wake for Task { fn wake(self: Arc<Self>) { QUEUE.lock().unwrap().push_back(self); } } fn block_on<F: Future<Output = ()> + Send + 'static>(fut: F) { spawn(fut); while let Some(task) = QUEUE.lock().unwrap().pop_front() { let waker = Waker::from(task.clone()); let mut cx = Context::from_waker(&waker); let mut fut = task.0.lock().unwrap(); let _ = fut.as_mut().poll(&mut cx); // this is the one line } }
// takeaways
Compiled to a state machine: no runtime vtable, no GC.
A Future does nothing on its own; the runtime relies on the waker to decide "when to poll again".
A single std::fs::read can stall an entire worker. Use spawn_blocking.
Further reading: tokio.rs/blog/2019-10-scheduler · rust-lang.github.io/async-book
github.com/lewis · @lewis on slack