Suspendable I/O crosses three ownership domains: generated Atoll state in a
Unit-instance arena, a Rust future driven by the Store reactor, and an
operating-system or database resource. ArenaIo coordinates those domains
without giving a native pointer an unbounded lifetime.
Instance boundary
Every Unit instance owns an ArenaIo state containing:
- a slab of in-flight operations;
- a generation-tagged slab of host resources;
- pending continuation resumes;
- an exact-slot readiness channel;
- generation pins or retained host buffers where required.
The state is per instance, not one process-global handle table. This keeps resource identity and cleanup aligned with Unit-instance eviction and migration.
Operation path
generated host call
→ validate arena handles and resource handle
→ reserve in-flight slot
→ construct or poll Rust future
→ pending: return to trampoline
→ exact-slot waker marks slot ready
→ Store polls that slot
→ encode result in current arena
→ enqueue ready-resume node
→ generated continuation resumesThe Store polls futures directly. It does not create one Tokio task for each operation, which keeps scheduling, cancellation, and Unit-instance ownership in one reactor.
Resume state
An in-flight slot remembers the target:
- TCB offset;
- resume function ID;
- continuation frame offset;
- operation-specific state;
- captured arena generation or relative buffer offsets;
- cancellation and cleanup state.
Completion becomes a ready-resume scheduler node. Generated code receives the standard result tag and value through its frame ABI.
Buffer models
Host operations use one of three buffer strategies.
Relative
The slot retains arena-relative offsets and lengths. On every poll it resolves them against the current arena body base. Arena growth is safe because the offsets survive relocation.
Pinned
An operation that must retain a raw WebAssembly address pins that exact arena generation. If the active arena moves, the old backing allocation remains on a retired list until the operation unpins its captured base.
The pin protects storage lifetime; it does not make simultaneous host and guest mutation safe. The operation contract still defines which side may access the range while suspended.
Deferred
Construct-at-drain operations retain data in host memory while pending. On completion they allocate and encode the result into the current arena generation. This avoids preserving a raw destination across suspension.
The choice is operation-specific. Documentation for a host function must state whether its input is copied, borrowed, pinned, or consumed.
Resource handles
Sockets, files, HTTP connections, WebSockets, SQL resources, and similar host objects live in a slab. A guest-visible 32-bit handle packs:
high 16 bits = resource generation
low 16 bits = slab indexClosing a resource removes the slab entry and advances its generation. A stale or double-close handle then fails validation instead of accidentally operating on a later resource that reused the same index.
The finite index and generation widths mean handles are capabilities for a runtime instance, not durable identifiers. They must not be persisted or sent to another Unit as opaque long-term data.
Cancellation
When a task or invocation is cancelled, the runtime drops or reaps its in-flight slots, removes their resume targets, and releases pins and temporary host data. Operation-specific cleanup may also close a half-created resource or roll back a held transaction.
Cancellation does not undo an external effect that completed before the runtime observed it. An I/O API must report indeterminate outcomes when, for example, a commit was accepted but its acknowledgement was lost.
Arena growth
Current runtimes expose pin, unpin, and retired-generation reclamation, so arena growth can proceed while suitable I/O is pending. The growth sequence preserves the old pinned allocation and publishes the new active base.
A legacy allocator without those exports uses the conservative rule: do not relocate while any I/O can retain a pointer. This fallback protects memory safety at the cost of delaying growth.
See Arenas for the complete generation protocol.
Migration
Migration first prevents new work, reaches a safepoint, and drains or cancels in-flight operations. Host resources are then considered individually.
Current transferable resource kinds include:
- TCP streams;
- TCP listeners;
- files;
- accepted HTTP connections, including socket and keepalive buffer.
Current nontransferable kinds include:
- WebSockets;
- active HTTP streams;
- SQL frames and open transactions;
- Polars resources.
Unsupported resources cause migration to be rejected. The runtime does not silently close them and claim state preservation.
For a supported resource, the source Store detaches it into an operating-system-level representation, preserves the slab key and generation, and reattaches it on the destination Store. The arena and resource table become visible at the destination only after exclusive source removal.
Errors
Host operations map completion into the runtime result tags:
OKcarries the operation result;ERRcarries an operation-defined error;CANCELLEDreports task cancellation;TIMEOUTreports a runtime timeout;ERR_FLATreports malformed flat input or result construction.
An error value that contains structured data is allocated in the current arena generation before the continuation resumes.
Safety rules
- validate every guest offset and length before host access;
- never retain a borrowed builder or raw pointer across unpinned relocation;
- pair every generation pin with cleanup on success, error, and cancellation;
- validate both resource index and generation;
- do not poll Unit-instance futures from a different Store reactor;
- reject migration when a resource has no faithful transfer protocol;
- state whether cancellation can leave an external outcome indeterminate.