You don't choose an async runtime. You inherit one. The moment you add reqwest, tonic, or sqlx to Cargo.toml, Tokio enters your dependency graph. In 2026, that's 150 million monthly downloads of gravity pulling every new project toward the same default. But defaults aren't decisions — and for low-latency services, the default can be the wrong tool.
The Landscape in 2026
Three runtimes remain standing. Tokio owns the ecosystem. smol owns the minimalist niche. glommio owns the thread-per-core frontier. async-std exited in March 2025 — its maintainers cited burnout and the impossibility of sustaining a second full-featured runtime without corporate backing. The experiment proved valuable: it pushed std-style APIs into Tokio and proved smol's executor could run real workloads. But the maintenance burden of duplicating timers, I/O drivers, and compatibility shims killed it.
| Runtime | Monthly Downloads | Architecture | Best For |
|---|---|---|---|
| Tokio | ~150M | Multi-threaded work-stealing | General purpose, ecosystem compatibility |
| smol | ~2.1M | Single-threaded + optional thread pool | Embedded, WASM, minimal deps |
| glommio | ~340K | Thread-per-core, io_uring | Ultra-low latency, storage engines, proxies |
Latency Characteristics
Tokio's work-stealing scheduler optimizes for throughput. Under load, it migrates tasks across threads — great for CPU-bound mixes, terrible for tail latency. A task parked on thread 3 waits for thread 7 to steal it. glommio eliminates this: each core runs one thread, pinned, with its own io_uring instance. No stealing. No cross-thread synchronization on the hot path. smol sits between: single-threaded by default, optional thread pool via async-executor, but no io_uring integration.
"Thread-per-core isn't faster — it's predictable. You trade peak throughput for bounded tail latency.
— glommio maintainer, 2024
Ecosystem Lock-In Is Real
Tokio's dominance isn't marketing — it's tokio::net::TcpStream implementing AsyncRead/AsyncWrite that every crate expects. hyper, tonic, redis-async, mongodb all assume Tokio types. glommio provides glommio::net::TcpStream with the same traits, but you'll hit friction: TLS via rustls needs tokio-rustls or async-rustls adapters. smol uses async-io which implements the same traits — easiest migration path, but no io_uring.
Decision Framework
Match the runtime to your constraint, not your preference.
| If your service... | Choose |
|---|---|
| Needs gRPC, HTTP/2, broad crate compatibility | Tokio |
| Runs on WASM, embedded, or must stay < 500KB | smol |
| Targets p99 < 1ms on Linux 5.10+, owns the hardware | glommio |
| Is a sidecar proxy, storage engine, or market data feed | glommio |
| Team has no Linux kernel tuning experience | Tokio |
Migration Path for Existing Tokio Services
You don't rewrite. You isolate. Extract the latency-critical path — the hot loop handling market data, the proxy request router, the storage flush — into a separate crate. Run it on glommio in its own process. Communicate via Unix domain sockets or shared memory. Keep the rest on Tokio. This is how Cloudflare, Datadog, and Materialize actually operate: heterogeneous runtime topologies, not monoliths.
✦
Start with Tokio. It's the safe, boring choice — and boring ships. But when p99 latency becomes a product requirement, not a curiosity, carve out the hot path and give it glommio. The runtime decision isn't forever. The architecture decision to isolate latency-critical code? That one lasts.

