Skip to content

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:

  1. 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.
  2. 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.
  3. 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.
  4. io_uring features — 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.
  5. 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_uring version markers here are kept consistent with the chronology, which was checked against a Linux v7.1-rc6 tree paired with liburing 2.15 — markers at 6.13 and 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).

SystemLanguageI/O modelBackend(s)Concurrency modelAbstraction layerLink
TokioRustReactor (+ experimental Proactor)mio (epoll/kqueue/IOCP); io-uring (unstable, files)Work-stealing M:N; poll-based Future/WakerRuntime / executortokio.md
GlommioRustProactorio_uring only (Linux); no reactor fallbackThread-per-core, share-nothing; async/.awaitRuntime / executorglommio.md
monoioRustProactor (+ Reactor fallback)io_uring; epoll/kqueue via mio fallbackThread-per-core, !Send; owned-buffer "rent"Runtime / executormonoio.md
Boost.AsioC++Proactor (emulated or native)epoll/kqueue/select (emulated); IOCP & io_uring (native)Completion tokens (callbacks/futures/coroutines)Runtime / executorboost-asio.md
SeastarC++Hybrid (pluggable)epoll (reactor); linux-aio & io_uring (proactor)Thread-per-core, share-nothing; future/promiseRuntime / executorseastar.md
libuvCReactor (+ Proactor)epoll/kqueue/event-ports; IOCP; io_uring (fs offload)Callbacks + worker threadpool; runtime owns loopDriver / reactor + runtimelibuv.md
Zig std.IoZigHybrid (vtable, per-backend)Threaded; Uring; Kqueue; Dispatch (GCD)Capability vtable; stackful fibers (evented)Driver vtable + runtimezig-io.md
.NET runtimeC#/CReactor (Proactor in PR)epoll/kqueue (SocketAsyncEngine); IOCP; io_uring (PR)Task/ValueTask over managed ThreadPoolRuntime / executordotnet.md
JavaJavaReactor / Proactor (varies)NIO epoll/kqueue/IOCP; JUring/Netty io_uring (FFI)NIO selectors; Loom virtual threadsDriver + runtimejava.md
Go runtimeGoReactorepoll/kqueue/IOCP netpoller (no io_uring)Integrated runtime; goroutines (M:N green threads)Integrated runtimego-netpoller.md
PythonPythonReactor (Proactor on Windows)selectors epoll/kqueue; libuv (uvloop); IOCPasync/await coroutines; Trio structured-concurrencyRuntime / executorpython-async.md
OCaml EioOCamlProactor (+ Reactor fallback)io_uring (eio_linux); poll/ppoll (eio_posix)Effect handlers; one-shot delimited continuationsEffect systemeio-backend.md
Cats EffectScalaReactor (+ Proactor PR)epoll/kqueue selector; io_uring (Netty/native PR)IO monad fibers; work-stealingEffect systemcats-effect · effects corpus
ZIOScalaReactorJVM NIO selectors / NettyZIO fibers; structured concurrencyEffect systemZIO · effects corpus
Effect-TSTypeScriptReactor (host loop)Node/Deno/Bun event loop (libuv et al.)Generators-as-fibers; structured concurrencyEffect systemEffect-TS · effects corpus
HaskellHaskellReactor (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 batchesIntegrated runtime + libraryhaskell.md
Lean 4LeanReactorlibuv (epoll/kqueue/event-ports/IOCP); no io_uringMonadic Task/IO.Promise; await = Task.bindRuntime / executorlean.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.

Systemepoll/kqueueIOCPio_uringWorker threadpoolio_uring depth (if used)
Tokio✅ (default)⚠️ unstable✅ (blocking pool)files only, behind tokio_unstable + io-uring
Glommio✅ onlydeep: 3 rings/CPU, registered buffers, IOPOLL for O_DIRECT NVMe
monoio✅ fallback✅ fallback✅ primaryowned-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 modelKernel tells youCanonical backendsSystems
Reactor (readiness)"fd is ready — you may syscall without blocking now"epoll, kqueue, event ports, poll/selectTokio (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 AIOGlommio, monoio (primary), OCaml Eio (Linux), Boost.Asio (model), JUring/Netty (Java)
HybridBoth, behind one API (often a reactor emulating a proactor, or pluggable backends)mixedlibuv, Seastar, Zig std.Io, Boost.Asio (emulated proactor over epoll)

io_uring can also be driven readiness-style via IORING_OP_POLL_ADD (especially its multishot form), which several runtimes use as a migration bridge — see the primitives readiness/completion paragraph and monoio's poll-io feature 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 modelSuspended computation is…Systems
CallbacksA registered continuation function invoked on the eventlibuv, Boost.Asio (callback token), asyncio's add_reader/add_writer floor (Python)
Futures / promisesA promise resolved by the loop; continuations chainedSeastar (future/promise), Boost.Asio (use_future), .NET (Task/ValueTask)
async/await state machinesA compiler-lowered state machine polled by an executorTokio, Glommio, monoio, Python (coroutines), C++20 coroutines on Boost.Asio
Stackful fibers / green threadsA parked stack resumed on its OS thread/carrierZig std.Io (evented backends), Java Loom virtual threads (Java), D's core.thread.Fiber (d-landscape)
Algebraic effects / continuationsA one-shot delimited continuation captured by a handlerOCaml 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 loopGo 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).

LayerConcernSystems / artifacts
Raw syscall bindingSQE/CQE building blocks, no schedulingD's during; liburing under libuv/Glommio/monoio; JUring's liburing FFI (Java)
Driver / reactorDemultiplex readiness/completion across OSesmio (under Tokio), libuv uv_loop_t, .NET SocketAsyncEngine (dotnet), Go netpoller, Zig Io.VTable, eventcore (d-landscape)
Runtime / executorSchedule tasks, drive the loop, integrate timers & blocking poolTokio, Glommio, monoio, Seastar, Boost.Asio, Python asyncio/Trio, Go
Effect systemExpress I/O & concurrency as a typed/handled effectOCaml 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.

Dateio_uring kernel milestoneEvent-loop / runtime milestone
2011libuv extracted from Node.js (epoll/kqueue/IOCP reactor)
2014–2015Seastar announced by ScyllaDB (thread-per-core C++)
Dec 2016Python asyncio stable in CPython 3.6 (PEP 3156); Tokio 0.1 announced (Rust readiness reactor over mio)
2017Trio 0.1 — nurseries & cancel scopes define structured concurrency
May 2019v5.1io_uring debuts (SQ/CQ rings, setup/enter/register; read/write/fsync/poll)
Jan 2020v5.5 — accept/connect, async-cancel, link-timeout
Mar 2020v5.6 — filesystem/syscall expansion; REGISTER_PROBE (feature detection)
May 2020v5.7 — splice, provided buffers, FEAT_FAST_POLL (competitive readiness)
Nov 2020Tokio 1.0 released
2020Glommio (Datadog) & Monoio (ByteDance) thread-per-core runtimes emerge
Jul 2022v5.19 — provided buffer rings, big SQE/CQE, IORING_OP_SOCKET
Oct 2022v6.0SEND_ZC zero-copy send, multishot recv, SETUP_SINGLE_ISSUER
Dec 2022v6.1SENDMSG_ZC, SETUP_DEFER_TASKRUN (the high-perf config)
2022–2023OCaml 5.0 ships effect handlers; Eio built on io_uring + effects
Sep 2023Java 21 LTSProject Loom virtual threads GA (JEP 444)
Jan 2024v6.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

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 & Flagsio_uring features 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

SystemOne-line
TokioRust's dominant runtime: readiness reactor over mio + work-stealing scheduler; unstable io_uring.
GlommioRust thread-per-core, io_uring-only proactor (Datadog), inspired by Seastar.
monoioRust thread-per-core completion runtime (ByteDance) with owned-buffer "rent" model.
Boost.AsioC++ Proactor reference; emulated over epoll, native on IOCP & io_uring.
SeastarC++ thread-per-core share-nothing framework (ScyllaDB); future/promise.
libuvC reactor behind Node.js; epoll/kqueue/IOCP + worker pool + io_uring fs offload.
Zig std.IoZig's new vtable I/O capability: Threaded/Uring/Kqueue/Dispatch backends.
.NETSocketAsyncEngine epoll reactor today; opt-in io_uring completion engine in PR.
JavaJDK NIO + JUring/Netty io_uring (Panama FFI) + Loom virtual threads.
GoRuntime netpoller + goroutines (epoll/kqueue/IOCP; no io_uring).
Pythonasyncio readiness loop + uvloop (libuv) + Trio structured concurrency.
OCaml Eioio_uring backend resuming OCaml 5 effect continuations from CQEs.
HaskellGHC RTS IO manager (epoll/kqueue green threads, MIO) + blockio-uring io_uring block I/O.
Lean 4Std.Async over libuv; each I/O op resolves an IO.Promise (a Task token); no io_uring.

Effects & synthesis


Sources

Each deep-dive carries its own primary-source citations; the authoritative artifacts behind this index's classifications are: