Async I/O & Event Loops
A breadth-first survey of asynchronous I/O and event-loop design across languages and runtimes, mapped against the Linux io_uring feature surface, to inform a @nogc/@safe, completion-first event-loop library for Sparkles ("event-horizon").
This survey answers five questions:
- Primitives — what building blocks does an event loop need, from the smallest loop that fires one timer to a fully-featured thread-per-core runtime? See primitives.
- Techniques — what do state-of-the-art runtimes build on top of the kernel's notification primitives (reactor/proactor, schedulers, wakers, timer wheels, buffer ownership, cancellation, feature probing)? See techniques.
- Cross-language design differences — how do Tokio, Glommio, monoio, Boost.Asio, Seastar, libuv, Zig
std.Io, .NET, Java, Go, Python, and OCaml Eio differ in I/O model, backend, and concurrency model? See the catalog and comparison. io_uringfeatures — exactly which operations, setup flags, and registration opcodes exist, and in which kernel version they landed, through Linux v7.1-rc6. See the io_uring reference.- Interplay with algebraic effect systems — how do effect handlers, fibers, and continuations resume suspended computations from a completion, and how does that relate to poll-based futures and green threads? See effects-and-event-loops and the algebraic-effects corpus.
Scope note. This is the master index for the async-io research tree. Each row links to a deep-dive that was written and fact-checked independently; where this index summarizes a system, the deep-dive is the source of truth.
io_uringversion markers here are kept consistent with the chronology, which was checked against a Linux v7.1-rc6 tree paired with liburing 2.15 — markers at6.13and later are forward-dated relative to general public knowledge ("as observed in this checkout").
Last reviewed: June 2, 2026
Master Catalog
One row per surveyed system. I/O model is the reactor/proactor classification from techniques (Reactor = readiness, Proactor = completion, Hybrid = both, behind one API). Abstraction layer places the system on the stack raw syscall binding → driver/reactor → runtime/executor → effect system (see the abstraction taxonomy).
| System | Language | I/O model | Backend(s) | Concurrency model | Abstraction layer | Link |
|---|---|---|---|---|---|---|
| Tokio | Rust | Reactor (+ experimental Proactor) | mio (epoll/kqueue/IOCP); io-uring (unstable, files) | Work-stealing M:N; poll-based Future/Waker | Runtime / executor | tokio.md |
| Glommio | Rust | Proactor | io_uring only (Linux); no reactor fallback | Thread-per-core, share-nothing; async/.await | Runtime / executor | glommio.md |
| monoio | Rust | Proactor (+ Reactor fallback) | io_uring; epoll/kqueue via mio fallback | Thread-per-core, !Send; owned-buffer "rent" | Runtime / executor | monoio.md |
| Boost.Asio | C++ | Proactor (emulated or native) | epoll/kqueue/select (emulated); IOCP & io_uring (native) | Completion tokens (callbacks/futures/coroutines) | Runtime / executor | boost-asio.md |
| Seastar | C++ | Hybrid (pluggable) | epoll (reactor); linux-aio & io_uring (proactor) | Thread-per-core, share-nothing; future/promise | Runtime / executor | seastar.md |
| libuv | C | Reactor (+ Proactor) | epoll/kqueue/event-ports; IOCP; io_uring (fs offload) | Callbacks + worker threadpool; runtime owns loop | Driver / reactor + runtime | libuv.md |
Zig std.Io | Zig | Hybrid (vtable, per-backend) | Threaded; Uring; Kqueue; Dispatch (GCD) | Capability vtable; stackful fibers (evented) | Driver vtable + runtime | zig-io.md |
| .NET runtime | C#/C | Reactor (Proactor in PR) | epoll/kqueue (SocketAsyncEngine); IOCP; io_uring (PR) | Task/ValueTask over managed ThreadPool | Runtime / executor | dotnet.md |
| Java | Java | Reactor / Proactor (varies) | NIO epoll/kqueue/IOCP; JUring/Netty io_uring (FFI) | NIO selectors; Loom virtual threads | Driver + runtime | java.md |
| Go runtime | Go | Reactor | epoll/kqueue/IOCP netpoller (no io_uring) | Integrated runtime; goroutines (M:N green threads) | Integrated runtime | go-netpoller.md |
| Python | Python | Reactor (Proactor on Windows) | selectors epoll/kqueue; libuv (uvloop); IOCP | async/await coroutines; Trio structured-concurrency | Runtime / executor | python-async.md |
| OCaml Eio | OCaml | Proactor (+ Reactor fallback) | io_uring (eio_linux); poll/ppoll (eio_posix) | Effect handlers; one-shot delimited continuations | Effect system | eio-backend.md |
| Cats Effect | Scala | Reactor (+ Proactor PR) | epoll/kqueue selector; io_uring (Netty/native PR) | IO monad fibers; work-stealing | Effect system | cats-effect · effects corpus |
| ZIO | Scala | Reactor | JVM NIO selectors / Netty | ZIO fibers; structured concurrency | Effect system | ZIO · effects corpus |
| Effect-TS | TypeScript | Reactor (host loop) | Node/Deno/Bun event loop (libuv et al.) | Generators-as-fibers; structured concurrency | Effect system | Effect-TS · effects corpus |
| Haskell | Haskell | Reactor (RTS) + Proactor (blockio-uring) | RTS IO manager epoll/kqueue/IOCP (MIO); io_uring for block I/O (blockio-uring) | Green threads (M:N) park on RTS loop; blockio-uring batches | Integrated runtime + library | haskell.md |
| Lean 4 | Lean | Reactor | libuv (epoll/kqueue/event-ports/IOCP); no io_uring | Monadic Task/IO.Promise; await = Task.bind | Runtime / executor | lean.md |
The three Scala/TypeScript effect libraries are surveyed in depth in the algebraic-effects corpus; the rows above cross-link rather than duplicate. For D's own existing options (vibe.d/eventcore, Photon,
during,core.thread.Fiber) and the gap a Sparkles loop would fill, see d-landscape.
Backend capabilities at a glance
A coarser cut of the same systems by which kernel facilities they actually exercise. "io_uring depth" sketches how far past the v5.1 baseline (read/write/fsync/poll) a backend reaches — see the features and timeline docs for the precise op-by-op support and the per-runtime configuration each adopts.
| System | epoll/kqueue | IOCP | io_uring | Worker threadpool | io_uring depth (if used) |
|---|---|---|---|---|---|
| Tokio | ✅ (default) | ✅ | ⚠️ unstable | ✅ (blocking pool) | files only, behind tokio_unstable + io-uring |
| Glommio | — | — | ✅ only | — | deep: 3 rings/CPU, registered buffers, IOPOLL for O_DIRECT NVMe |
| monoio | ✅ fallback | ✅ fallback | ✅ primary | — | owned-buffer "rent" model; deferred batched SQE submission; poll-io bridge |
| Boost.Asio | ✅ (emulated proactor) | ✅ native | ✅ native (Linux) | ✅ (resolver, files) | io_uring_service for file & descriptor ops |
| Seastar | ✅ (reactor backend) | — | ✅ (proactor backend) | ✅ (syscall thread) | pluggable; linux-aio or io_uring; userspace busy-poll |
| libuv | ✅ (default) | ✅ | ✅ (fs offload) | ✅ (fs/DNS/getaddrinfo) | filesystem-op offload only; sockets stay on epoll |
Zig std.Io | ✅ (Kqueue) | — | ✅ (Uring) | ✅ (Threaded) | full Uring backend pairs io_uring with stackful fibers |
| .NET | ✅ (default) | ✅ | ⚠️ PR only | ✅ (ThreadPool) | opt-in completion engine (PR #124374), not merged |
| Java | ✅ (NIO) | ✅ | ✅ (JUring/Netty FFI) | ✅ (NIO.2 group) | FFI to liburing: fixed buffers, registered files, multishot accept |
| Go | ✅ (netpoller) | ✅ | — | ✅ (cgo/syscall) | not used — netpoller is readiness-only |
| Python | ✅ (selectors) | ✅ (Proactor) | — (no stdlib) | ✅ (executor) | none in stdlib; uvloop uses libuv (which offloads fs to io_uring) |
| OCaml Eio | ✅ (eio_posix poll) | — | ✅ (eio_linux) | — | full backend; CQE carries the suspended continuation tag |
Taxonomy
By I/O model
The single most important axis is who issues the I/O syscall and who gets told — the Reactor (readiness) vs Proactor (completion) split, named in Schmidt et al.'s POSA2 patterns. See techniques and primitives for the full treatment.
| I/O model | Kernel tells you | Canonical backends | Systems |
|---|---|---|---|
| Reactor (readiness) | "fd is ready — you may syscall without blocking now" | epoll, kqueue, event ports, poll/select | Tokio (default), Go, Python (Unix), .NET (today), Java NIO, Cats Effect, ZIO, Effect-TS |
| Proactor (completion) | "your operation finished — here is the result/bytes" | io_uring, IOCP, POSIX AIO | Glommio, monoio (primary), OCaml Eio (Linux), Boost.Asio (model), JUring/Netty (Java) |
| Hybrid | Both, behind one API (often a reactor emulating a proactor, or pluggable backends) | mixed | libuv, Seastar, Zig std.Io, Boost.Asio (emulated proactor over epoll) |
io_uringcan also be driven readiness-style viaIORING_OP_POLL_ADD(especially its multishot form), which several runtimes use as a migration bridge — see the primitives readiness/completion paragraph and monoio'spoll-iofeature in techniques.
By concurrency model
How a suspended computation is represented and resumed when the I/O completes. See effects-and-event-loops for how completions map back onto each representation.
| Concurrency model | Suspended computation is… | Systems |
|---|---|---|
| Callbacks | A registered continuation function invoked on the event | libuv, Boost.Asio (callback token), asyncio's add_reader/add_writer floor (Python) |
| Futures / promises | A promise resolved by the loop; continuations chained | Seastar (future/promise), Boost.Asio (use_future), .NET (Task/ValueTask) |
async/await state machines | A compiler-lowered state machine polled by an executor | Tokio, Glommio, monoio, Python (coroutines), C++20 coroutines on Boost.Asio |
| Stackful fibers / green threads | A parked stack resumed on its OS thread/carrier | Zig std.Io (evented backends), Java Loom virtual threads (Java), D's core.thread.Fiber (d-landscape) |
| Algebraic effects / continuations | A one-shot delimited continuation captured by a handler | OCaml Eio (Effect.Deep), and the broader effects corpus (Cats Effect, ZIO, Effect-TS, Koka) |
| Integrated runtime (goroutines) | A goroutine parked by the runtime; no user-visible loop | Go netpoller + G-M-P scheduler |
By abstraction layer
Where a system sits on the stack. Higher layers are built atop lower ones; many systems span two layers (e.g. libuv is both a reactor and a runtime).
| Layer | Concern | Systems / artifacts |
|---|---|---|
| Raw syscall binding | SQE/CQE building blocks, no scheduling | D's during; liburing under libuv/Glommio/monoio; JUring's liburing FFI (Java) |
| Driver / reactor | Demultiplex readiness/completion across OSes | mio (under Tokio), libuv uv_loop_t, .NET SocketAsyncEngine (dotnet), Go netpoller, Zig Io.VTable, eventcore (d-landscape) |
| Runtime / executor | Schedule tasks, drive the loop, integrate timers & blocking pool | Tokio, Glommio, monoio, Seastar, Boost.Asio, Python asyncio/Trio, Go |
| Effect system | Express I/O & concurrency as a typed/handled effect | OCaml Eio, Cats Effect, ZIO, Effect-TS, Koka (see effects corpus) |
Milestones
A high-confidence timeline interleaving io_uring kernel milestones (cross-checked against the chronology) with event-loop / runtime milestones. Dates at 6.13 and later are forward-dated "as observed in the v7.1-rc6 checkout" — see the timeline for the caveat.
| Date | io_uring kernel milestone | Event-loop / runtime milestone |
|---|---|---|
| 2011 | — | libuv extracted from Node.js (epoll/kqueue/IOCP reactor) |
| 2014–2015 | — | Seastar announced by ScyllaDB (thread-per-core C++) |
| Dec 2016 | — | Python asyncio stable in CPython 3.6 (PEP 3156); Tokio 0.1 announced (Rust readiness reactor over mio) |
| 2017 | — | Trio 0.1 — nurseries & cancel scopes define structured concurrency |
| May 2019 | v5.1 — io_uring debuts (SQ/CQ rings, setup/enter/register; read/write/fsync/poll) | — |
| Jan 2020 | v5.5 — accept/connect, async-cancel, link-timeout | — |
| Mar 2020 | v5.6 — filesystem/syscall expansion; REGISTER_PROBE (feature detection) | — |
| May 2020 | v5.7 — splice, provided buffers, FEAT_FAST_POLL (competitive readiness) | — |
| Nov 2020 | — | Tokio 1.0 released |
| 2020 | — | Glommio (Datadog) & Monoio (ByteDance) thread-per-core runtimes emerge |
| Jul 2022 | v5.19 — provided buffer rings, big SQE/CQE, IORING_OP_SOCKET | — |
| Oct 2022 | v6.0 — SEND_ZC zero-copy send, multishot recv, SETUP_SINGLE_ISSUER | — |
| Dec 2022 | v6.1 — SENDMSG_ZC, SETUP_DEFER_TASKRUN (the high-perf config) | — |
| 2022–2023 | — | OCaml 5.0 ships effect handlers; Eio built on io_uring + effects |
| Sep 2023 | — | Java 21 LTS — Project Loom virtual threads GA (JEP 444) |
| Jan 2024 | v6.7 — futex, waitid, read-multishot | — |
| May 2025* | v6.15 — zero-copy receive (RECV_ZC + ZCRX_IFQ), EPOLL_WAIT, vectored fixed I/O | — |
| 2025–2026 | — | .NET io_uring engine proposed (PR #124374, opt-in, not merged); Zig std.Io vtable in development for 0.16 |
| Apr 2026* | v7.0 — SQ rewind | — |
| May 2026* | v7.1-rc6 — current checkout (liburing 2.15) | This survey's ground truth |
* Forward-dated relative to general public knowledge — see the timeline caveat.
Quick Navigation
Suggested reading paths
- "I want the model first." primitives → techniques → io_uring programming model → one proactor deep-dive (Glommio or monoio).
- "I want the
io_uringdetails." io_uring programming model → features → opcodes reference → timeline. - "I want the cross-language comparison." Master catalog → comparison → individual rows of interest.
- "I care about effects & resumption." effects-and-event-loops → OCaml Eio backend → algebraic-effects corpus.
- "I'm designing the Sparkles loop." d-landscape → comparison → techniques (buffer ownership, cancellation, feature probing).
Concepts
- Event-Loop Primitives — the layered checklist (Tier 0 → Tier 3), readiness vs completion mapped per primitive.
- Implementation Techniques — reactor/proactor, ring batching, scheduler topologies, wakers, timer wheels, buffer ownership, cancellation, feature probing.
- Comparison — the head-to-head matrix and design recommendations for Sparkles.
- Effects & Event Loops — how completions resume fibers, continuations, and effect handlers.
- D Landscape — existing D options (vibe.d/eventcore, Photon,
during,core.thread.Fiber) + gap analysis.
io_uring reference
- Programming Model — rings, SQE/CQE layout, syscalls, operating modes (SQPOLL/IOPOLL/
DEFER_TASKRUN/io-wq). - Features & Flags —
io_uringfeatures grouped by semantic area. - Timeline — kernel-version chronology v5.1 → v7.1-rc6 + a feature/version/library matrix.
- Opcodes Reference — full
IORING_OP_*/ flag reference tables.
Library deep-dives
| System | One-line |
|---|---|
| Tokio | Rust's dominant runtime: readiness reactor over mio + work-stealing scheduler; unstable io_uring. |
| Glommio | Rust thread-per-core, io_uring-only proactor (Datadog), inspired by Seastar. |
| monoio | Rust thread-per-core completion runtime (ByteDance) with owned-buffer "rent" model. |
| Boost.Asio | C++ Proactor reference; emulated over epoll, native on IOCP & io_uring. |
| Seastar | C++ thread-per-core share-nothing framework (ScyllaDB); future/promise. |
| libuv | C reactor behind Node.js; epoll/kqueue/IOCP + worker pool + io_uring fs offload. |
Zig std.Io | Zig's new vtable I/O capability: Threaded/Uring/Kqueue/Dispatch backends. |
| .NET | SocketAsyncEngine epoll reactor today; opt-in io_uring completion engine in PR. |
| Java | JDK NIO + JUring/Netty io_uring (Panama FFI) + Loom virtual threads. |
| Go | Runtime netpoller + goroutines (epoll/kqueue/IOCP; no io_uring). |
| Python | asyncio readiness loop + uvloop (libuv) + Trio structured concurrency. |
| OCaml Eio | io_uring backend resuming OCaml 5 effect continuations from CQEs. |
| Haskell | GHC RTS IO manager (epoll/kqueue green threads, MIO) + blockio-uring io_uring block I/O. |
| Lean 4 | Std.Async over libuv; each I/O op resolves an IO.Promise (a Task token); no io_uring. |
Effects & synthesis
- Effects & Event Loops — the synthesis tying completion-driven resumption to fibers, continuations, and algebraic effects.
- Algebraic Effects corpus — the parallel research tree; deep-dives for Cats Effect, ZIO, Effect-TS, Koka, Loom, OCaml effects.
- Comparison — cross-system matrix + Sparkles design recommendations.
- D Landscape — what D already has and what an
io_uring-first Sparkles loop would add.
Sources
Each deep-dive carries its own primary-source citations; the authoritative artifacts behind this index's classifications are:
- io_uring — Linux UAPI header
include/uapi/linux/io_uring.h(v7.1-rc6 tree), liburing man pages (io_uring_enter/setup/register), Jens Axboe's "Efficient IO with io_uring", and LWN's "The rapid growth of io_uring". See the chronology for per-version provenance. - Reactor/Proactor taxonomy — Schmidt et al., Pattern-Oriented Software Architecture, Vol. 2 (POSA2), as applied in techniques.
- Per-system sources — repository trees, official docs, and design write-ups cited in each linked deep-dive (Tokio, Glommio, monoio, Boost.Asio, Seastar, libuv, Zig, .NET, Java, Go, Python, OCaml Eio).