The WASI Subgroup voted to ratify WASI 0.3.0, and the change sitting under the version number is that stream<T>, future<T>, and async are now first-class constructs in the WebAssembly Component Model's canonical ABI. The host owns a single event loop, shared by every component it runs, and the runtime does the scheduling.
That reframes what a WASI version means here. In 0.2 each component carried its own event loop and async runtime. A single component could run on a host, but two of those loops had no way to coordinate. Any component that touched streaming or async APIs could not be composed with another component at all. The wasi:io package (pollables, input-streams, output-streams) encoded async behaviour by hand because the ABI gave it nowhere else to put it.
In 0.3 those primitives move into the ABI itself. When a value is delivered to a future, the runtime wakes whatever task is awaiting it, even if that future crossed several component boundaries to get there. The writer might be the host, another component, or the same component that holds the read end. stream<T> and future<T> behave like resource types: each is an owned handle, and passing one across a boundary transfers ownership from caller to callee. Unlike resource types, they cannot be borrowed.
The model is completion-based rather than readiness-based, closer to Linux io_uring and Windows IoRing than to epoll or kqueue. A readiness-style API can be emulated on top for code that needs it. Components now export and import async functions directly, which retires the 0.2 three-step start-foo / finish-foo / subscribe dance.
The stream status fix
0.2 surfaced terminal errors inline on each read of a stream. A caller only learned the outcome by continuing to read, so a reader that stopped early could not tell a clean close from an error. 0.3 streams return an extra future alongside the stream that resolves independently of how much you consume:
// WASI 0.2
read-via-stream: func() -> result<input-stream, error-code>;
// WASI 0.3
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
Most of the other interface changes are mechanical in the same way: the same shapes as before, with simpler signatures now that the runtime handles the async part.
Bindings follow the ABI
Because async lives in the ABI, binding generators can emit async that reads as native in each language. wasi:http/handler exposes one function, handle, marked async; the Rust wit-bindgen crate maps it to an async fn handle. The ABI was designed to carry both stackless and stackful coroutines side by side. Python, JavaScript, C#, and C (stackless) support is in progress. Go is the stackful case: its runtime converts synchronous-looking calls, parking a goroutine at the ABI boundary and resuming it when the stream is ready, so blocking calls inside a goroutine stream a body without blocking the rest of the program.
wasi:http and service chaining
wasi:http saw the most change, and not the mechanical kind. It now exposes two worlds. service imports client and exports handler. middleware is a superset that also imports handler, so it can forward requests to another handler. middleware replaces the 0.2 proxy world and enables service chaining: components composed directly with one another instead of talking over the network. For microservices that call each other often, a runtime can wire them together inside the same process. The projection is a drop from milliseconds to nanoseconds per call, six orders of magnitude.
What is shipping
0.3.0 is a stable release. Programs compiled for it today are guaranteed to keep working, with patch releases every two months on the 0.3.x line. Wasmtime 45 runs the latest release candidate now; Wasmtime 46 ships 0.3.0 with Component Model Async enabled by default. jco, the JavaScript Component Model toolchain, supports all of 0.3, with a default-on release due soon. Guest toolchains for Rust, Go, JavaScript, Python and others are next, and individual projects will announce 0.3 support over the coming weeks and months.
Top comments (0)