I/O Reactor (std::io_runtime)
std::io_runtime gives access to Zolo's I/O reactor — the same event loop that
serves TCP connections, async file reads, and other I/O. On Linux >= 5.19 it
uses io_uring; on macOS, Windows, and older kernels it falls back to the
fallback(thread-park) backend. Because it depends on the host runtime, it
cannot be run in the WASM sandbox; use the Zolo CLI.
For a simple pause with no other I/O in flight, process.sleep(ms) is more
straightforward. Use io_runtime when the program is already driving the reactor
and you want the timer to coexist with other I/O on the same loop.
One-shot timer: sleep_ms
io_runtime.sleep_ms(ms) schedules a one-shot timer and blocks until it fires,
yielding control to the reactor (no busy spin). io_runtime.backend() reports
which implementation was selected at runtime:
Checks the backend and measures the time of a sleep_ms(100) with os.clock.
// Feature: io_runtime.sleep_ms — reactor-backed timer
// Syntax: io_runtime.sleep_ms(ms: int)
// When to use: when the program is already driving the zolo-io reactor
// (TCP server, file IO, other timers) and you want the same loop turn
// to deliver this sleep alongside whatever else is in flight. For a
// pure pause with no other IO, `process.sleep` / `scheduler.sleep`
// (which use OS thread::sleep) are simpler.
use std::io_runtime
use std::os
// Confirm which backend the runtime resolved to. On Linux ≥ 5.19 you
// see "zolo-io/uring(multishot-accept+timerfd+eventfd)"; on macOS,
// Windows, and older Linux kernels you see "zolo-io/fallback(thread-park)".
print("backend: {io_runtime.backend()}")
// Schedule a one-shot timer for 100ms and block until it fires. The
// reactor handles the actual wait via timerfd (Linux) or condvar
// (fallback) — this Lua-side loop is not a busy spin.
let start = os.clock()
io_runtime.sleep_ms(100)
let elapsed_ms = (os.clock() - start) * 1000.0
print("elapsed: {elapsed_ms}ms") // expected: ~100 (±10ms wakeup overhead)
Requires the Zolo CLI/host — open in the playground or run locally.
Periodic timers: every_ms, wait_next, and cancel
io_runtime.every_ms(interval, tag) arms a recurring timer identified by a
numeric tag. io_runtime.wait_next() blocks via the reactor and returns the next
event { kind, user_data }. Multiple timers share the same loop; firings that
arrive together are queued so none are lost. Always cancel with
io_runtime.cancel(handle) when leaving scope:
Two concurrent timers (50 ms and 150 ms); collects 6 events and cancels both.
// Feature: io_runtime.every_ms + wait_next + cancel — recurring timers
// When to use: build a simple event loop with multiple periodic timers
// (heartbeat, metrics flush, watchdog) without spawning OS threads.
// The reactor batches all fires into one `io_uring_enter` syscall on
// Linux; the fallback uses a condvar park.
use std::io_runtime
// Two concurrent timers — distinguish via the optional `tag` arg which
// is echoed in each event's `user_data` field.
let fast = io_runtime.every_ms(50, 1)
let slow = io_runtime.every_ms(150, 2)
var fires = 0
var fast_count = 0
var slow_count = 0
// Pull events one at a time. wait_next() blocks via the reactor, so
// the loop is not a busy spin. Events that arrive in the same kernel
// batch are queued internally and handed back on successive calls so
// concurrent timer fires never silently disappear.
while fires < 6 {
let ev = io_runtime.wait_next()
if ev.kind == "timer" {
if ev.user_data == 1 {
fast_count += 1
}
if ev.user_data == 2 {
slow_count += 1
}
}
fires += 1
}
// Always cancel timers before leaving the scope — the reactor would
// keep them armed otherwise. Idempotent / safe to call twice.
io_runtime.cancel(fast)
io_runtime.cancel(slow)
print("fast fires: {fast_count}") // expected: ~5 (50ms × 5 < 300ms)
print("slow fires: {slow_count}") // expected: ~1-2 (150ms × 2 ≈ 300ms)
print("total events: {fires}") // expected: 6
Requires the Zolo CLI/host — open in the playground or run locally.
Challenge
Add a third timer at 200 ms with tag 3 and adjust the loop to collect 10
events. How many firings of the slow timer do you expect?
See also