A minimal async runtime for macOS, written from scratch in Rust with no dependencies beyond libc.
Built to understand how async actually works — Future, Waker, the executor, the reactor, and the syscall boundary. Every kernel call is a raw unsafe FFI call; nothing is wrapped by mio, nix, or tokio. It runs real async/await code on real std::future::Futures driven by a hand-rolled Waker.
Runs multiple async tasks concurrently on a single thread — including a TCP echo server — sleeping in the kernel while they wait instead of busy-polling.
let mut executor = Executor::new();
// An async TCP echo server: one spawned task per connection.
executor.spawn(accept_loop("127.0.0.1:8080".to_string()));
// A task that sleeps on a real kqueue timer, then prints.
executor.spawn(async {
TimerFuture::new(Duration::from_secs(2)).await;
println!("2s elapsed");
});
executor.run();Future+Waker— tasks are realstd::future::Futures. AWakeris hand-built from aRawWakerVTable(waker.rs); its only job iswake()→ push this task's id back onto the ready queue. That single line is whatasync/awaitultimately bottoms out on.- Executor (
executor.rs) — drivesPin<Box<dyn Future>>with aContext, drops tasks that returnReady, and blocks in the reactor when everything left isPending. Supportsspawn()(e.g. one echo task per accepted socket) andyield_now()for cooperative CPU tasks. - Reactor (
reactor.rs) — owns onekqueuefd. RegistersEVFILT_TIMER/EVFILT_READinterest without blocking, stores anident → Wakermap, andwait()blocks inkeventuntil events fire, then wakes exactly those tasks. - Leaf futures —
TimerFuture(timer.rs) andAccept/ReadFut(net.rs). On a would-block, each stashes itsWakerwith the reactor, returnsPending, and gets re-polled only when its kernel event fires.
The kernel never runs any of this code. It stores an integer (ident) and hands it back when the event fires. Translating that integer into "wake this task" is the reactor + waker's job.
The whole point of a reactor: while tasks wait, the process should use zero CPU, not spin.
| Executor | User CPU while idle |
|---|---|
| Busy-poll loop (old design) | ~1 full core |
Blocking kevent (this) |
0.0% |
Same work — the spinning version burns a core to accomplish nothing.
cargo run
# in another terminal:
printf 'hello' | nc 127.0.0.1 8080 # -> echoes "hello" backWorking: real Future/Waker, async/await, kqueue reactor, timer futures, async TCP accept + echo, task spawning, cooperative yielding.
Not yet:
- Async
write(currently best-effort synchronous). JoinHandle/ awaiting a task's return value.- Combinators (
select,timeout,join). - Multi-threading (work-stealing executor), cancellation.
macOS/BSD only — kqueue is the backend. Linux would need epoll.