std/event
The OS readiness poller behind the scheduler — kqueue on macOS, epoll on Linux, IOCP on Windows — plus the non-blocking fd flag every one of them requires.
from "std/event" import { EventLoop, eventLoopClose, setNonblocking, eventRegisterRead, eventPoll }This is the lowest layer of Milo's async I/O. std/runtime owns an EventLoop and drives it for you, so importing this module means you are writing a primitive the stdlib does not already provide — a poller over fds no std type wraps, or an integration with a C library's own descriptor.
std/event is a platform split: std/event.darwin.milo, std/event.linux.milo and std/event.windows.milo all export the same names and the resolver picks by target OS. Write from "std/event", never the suffixed path.
Types
EventLoop
struct EventLoopA kqueue/epoll/IOCP handle. EventLoop.new() returns Result<EventLoop, string>; eventLoopClose releases it.
Non-blocking fds
fn setNonblocking(fd: i32): i32
fn clearNonblocking(fd: i32): i32Returns 0 on success, -1 on error. Registering an fd that is still blocking is the usual cause of a poller that reports ready and then hangs in read: readiness says a byte was available, not that the next read will return.
Registering and polling
fn eventRegisterRead(el: &EventLoop, fd: i32): i32
fn eventRegisterWrite(el: &EventLoop, fd: i32): i32
fn eventDeregister(el: &EventLoop, fd: i32, forWrite: bool): i32
fn eventPoll(el: &EventLoop, readyFds: *i32, maxEvents: i32, timeoutMs: i32): i32eventPoll fills readyFds — a caller-allocated [i32; N] with N >= maxEvents, passed bare (a fixed array coerces to *i32) — and returns how many fds are ready, or -1 on error. A negative timeoutMs blocks indefinitely.
let fd: i32 = 0 // stdin, or any descriptor you own
var el = EventLoop.new()!
setNonblocking(fd)
eventRegisterRead(el, fd)
var ready: [i32; 8] = [0; 8]
let n = eventPoll(el, ready, 8, 1000)
eventLoopClose(el)Cross-task wakeups
fn eventLoopInitWakeup(el: &EventLoop): i32
fn eventLoopNotify(el: &EventLoop, wakeupId: i32): i32
fn eventLoopDrainWakeup(el: &EventLoop, wakeupId: i32): void
fn eventLoopCloseWakeup(el: &EventLoop, wakeupId: i32): voidA self-pipe that breaks a blocked eventPoll from elsewhere — how a Promise.blocking worker tells the scheduler its result is ready. eventLoopInitWakeup returns the id to pass to the others.
Adopting a foreign loop
fn eventLoopFd(el: &EventLoop): i32
fn eventLoopFromFd(fd: i32): EventLoopFor embedding: hand Milo's loop fd to another runtime's poller, or wrap a descriptor that runtime already owns. eventLoopFromFd does not take ownership — closing it is still the original owner's job.