runite
runite is an event-loop-per-thread, non-work-stealing async runtime for
Rust. Each runtime thread owns its own scheduler, timer heap, and platform I/O
driver. io_uring on Linux, kqueue on macOS, and IOCP on Windows. It uses
JavaScript-style microtask/macrotask scheduling to give deterministic flush
points.
It is built for UI front-ends, embedded event loops, and fine-grained reactive
systems rather than as a general-purpose high-throughput server runtime. It
deliberately prefers simple per-thread event loops, thread-local state, and
predictable scheduling over work-stealing, Send-future ergonomics, and maximum
I/O throughput.
Status: pre-release. APIs may change before 1.0.
Platform support
| Platform | Backend | Status |
|---|---|---|
Linux x86_64 |
io_uring |
Primary |
Linux aarch64 |
io_uring |
Supported |
macOS aarch64 |
kqueue + offload |
Supported |
Windows x86_64 |
IOCP + offload | Supported |
The Windows backend drives sockets, files, and child-process pipes with
overlapped I/O through one I/O completion port per runtime thread, offloading
to the blocking pool only where Windows has no asynchronous form (open,
metadata, directory scans, DNS, console stdio). See
docs/WINDOWS.md for the design. Windows-only differences:
runite::fd (descriptor readiness) and runite::net::unix (Unix-domain
sockets) are not available, TcpSocket::set_reuseport reports
ErrorKind::Unsupported, and runite::signal::windows replaces
runite::signal::unix (the portable runite::signal::ctrl_c works on all
platforms). Unsupported targets fail to compile with a clear error.
Minimum Linux kernel
The io_uring backend targets Linux 6.1+ (current LTS), which is what CI tests. Older kernels may work subject to these io_uring feature requirements:
- 5.6 — base ring operations (
openat/read/write/fsync/statx/…); required. - 5.18 —
MSG_RING, used for cross-thread wakeups; required for multithreaded runtimes (spawn_worker), optional for a single event loop. - File truncation (
OpenOptions::truncate,File::set_len) usesFTRUNCATE(6.9) and falls back toftruncate(2)on older kernels. - Socket operations (
socket5.19,bind/listen6.11, andconnect/accept/send/recv/shutdown) transparently fall back to blocking syscalls on kernels that lack the opcode, so networking works below these versions with reduced native-io_uring coverage.
So the recommended 6.1 floor exercises every feature; the only hard lower bounds are 5.6 (single-threaded) and 5.18 (multithreaded).
Installation
[]
= "0.1"
Quick start
async
You can also use a synchronous entry point and drive the loop yourself:
What you get
- Entry points:
#[runite::main](works onfn mainorasync fn main),#[runite::test], andblock_onfor driving one future to completion. - Event loop:
run,run_until_stalled,run_ready_tasks,queue_macrotask,queue_microtask,spawn,yield_now. - Workers:
spawn_workerplus theSend-only cross-threadThreadHandle::queue_macrotask. - Tasks: spawned futures return
JoinHandle<T>that awaits toResult<T, JoinError>; useabort,abort_handle,is_finished, and cloneableAbortHandles for cancellation, andtask::JoinSetfor structured ownership of a group of local tasks. - Timers:
time::set_timeoutandtime::set_interval(each returns a handle with.cancel()), plustime::{sleep, timeout, interval}wheretime::intervalis the awaitable interval. - I/O: async
fs,net(TCP/UDP everywhere; Unix-domain sockets on Unix),stdio, and crate-localAsyncRead/AsyncWrite/Streamtraits with extension adapters; TCP split/reunite, listenerincoming()streams, async stdin/stdout/stderr, andBufReader/BufWriter. - Processes:
process::{Command, Child}with piped async stdio,kill, andwait. - Channels & sync:
channel::{mpsc, oneshot, broadcast, watch},sync::{Mutex, RwLock, Semaphore, Notify, OnceCell}. - Blocking offload:
spawn_blockingonto a bounded shared OS-thread pool. - Signals: portable
signal::ctrl_c, async Unix signal handling (including SIGWINCH viaSignalKind::WindowChange), and Windows console control events (signal::windows).
Scaling across cores
runite is event-loop-per-thread: each runtime thread drives its own local scheduler and
accepts !Send futures. To scale CPU-bound or server workloads across cores, start one
event loop per core with spawn_worker; on Linux and macOS, servers should bind per-core accept
loops with SO_REUSEPORT so the OS distributes inbound connections. See ARCHITECTURE.md
for the full threading and scaling model.
Feature flags
| Feature | Default | Description |
|---|---|---|
hyper |
off | hyper 1.x integration: transport impls for TcpStream (and UnixStream on Unix) plus the hyper_rt executor/timer for server and HTTP/2 use. |
futures-compat |
off | io::compat adapters to/from the futures-io traits. |
Configuration
| Environment variable | Effect |
|---|---|
RUNITE_BLOCKING_THREADS |
Size of the shared blocking-task pool (clamped 1..=32). |
RUNITE_REMOTE_QUEUE_CAPACITY |
Bound on the per-thread cross-thread macrotask queue (default 65536). |
Examples
Start with these — each one demonstrates a reason the event-loop-per-thread model exists, not just an API:
| Example | What it shows |
|---|---|
command_center |
An interactive terminal app: async stdin, background jobs, and shared Rc<RefCell> state on one loop that never blocks on you. Interactive, or -- --demo. |
chat_server |
A collaborative-session backend whose entire room state is Rc<RefCell<HashMap>> — no Arc, no Mutex, no Send bounds — plus Ctrl-C graceful shutdown. Interactive (nc in!), or -- --demo. |
background_workers |
The Web-Workers discipline: CPU work on the blocking pool while a heartbeat measures that the loop stayed responsive. Run with -- --blocking to see the jank, quantified. |
frame_loop_embedding |
runite as a guest inside a host frame loop (GUI/game shape): run_until_stalled() per frame, requestAnimationFrame-style tasks, render from settled state. |
build_pipeline |
Dev-tool process orchestration: bounded-concurrency subprocess fan-out with Command::output, where a failing step is data, not a crash. |
Feature tours of specific APIs:
Architecture
See ARCHITECTURE.md for the threading model, micro/macro task scheduling, run lifecycle, cancellation and buffer-ownership rules, the driver abstraction, the platform parity matrix, and the documented safety invariants.
Development
The toolchain is pinned with mise. Install it, then:
Individual tasks:
| Task | Command | Purpose |
|---|---|---|
mise run build |
cargo build --workspace --all-targets |
Build the workspace. |
mise run test |
cargo test --workspace --all-features |
Unit, integration, and doctests. |
mise run lint |
cargo clippy --workspace --all-targets --all-features -- -D warnings |
Lint with warnings denied. |
mise run bench |
cargo bench --workspace --all-features |
Criterion benchmarks (benches/). |
mise run coverage |
cargo llvm-cov --workspace --all-features ... |
HTML + lcov coverage report. |
mise run cop |
cop cop-checks/main.cop -t . |
Agent Cop static-analysis checks. |
Testing
Integration tests live in tests/ and drive the public API end to end (TCP/UDP echo,
filesystem round trips, cross-thread workers and channels) via a block_on helper that
runs each future on a dedicated event-loop thread.
Benchmarks
benches/runtime.rs measures executor mechanics (task spawn, yield, channels, timers) and
benches/io.rs measures loopback TCP and filesystem throughput, using
criterion. Run a single benchmark with:
Profiling and observability
runite emits tracing spans/events on these targets, usable for
latency investigation with any tracing subscriber:
| Target | Covers |
|---|---|
runite::driver |
io_uring / kqueue / IOCP submission and completions |
runite::runtime |
runtime and worker lifecycle |
runite::scheduler |
task scheduling and cross-thread queueing |
runite::timer |
timer arming/firing (debug builds) |
runite::async |
future polling and cancellation (debug builds) |
For CPU profiling, build with --release and use perf / cargo flamegraph against an
example or benchmark binary.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.