Skip to content

runtime-wasmtime: bridge wasi:io streams to stream<u8>#1262

Open
cargopete wants to merge 1 commit into
bytecodealliance:mainfrom
cargopete:feat/wasi-io-stream-bridge
Open

runtime-wasmtime: bridge wasi:io streams to stream<u8>#1262
cargopete wants to merge 1 commit into
bytecodealliance:mainfrom
cargopete:feat/wasi-io-stream-bridge

Conversation

@cargopete
Copy link
Copy Markdown
Contributor

@cargopete cargopete commented May 30, 2026

Closes #185.
Closes #184 (the async-path computation over Wasmtime component types is exactly part 1 below).

wrpc-runtime-wasmtime is meant to send/receive wasi:io input-stream/output-stream resources as native wRPC stream<u8>. Most of the machinery existed but was untested; this PR makes it work end-to-end and adds a runnable example.

Bugs fixed

Three distinct issues were blocking the path, found by building the example below and following the failures:

  1. Subscription paths were never set. Both serve_function and serve_function_shared called self.serve(.., []) (the two // TODO: set paths markers), so the transport never subscribed to a stream parameter's data channel — invocations failed with `[0]` subscription not found. Added paths::params_async_paths, which walks the Wasmtime component parameter types (mirroring wrpc_introspect::async_paths_ty) and computes the async subscription paths, treating wasi:io streams as stream<u8>.

  2. wasi:io stream resources were misclassified during decode/encode. At serve time a stream parameter appears as the component's uninstantiated resource type, which does not equal ResourceType::host::<DynInputStream>(), so the codec fell through to the guest-resource branch and tried to read a resource handle instead of stream data. The runtime now collects the component's imported wasi:io/streams resource types up front (paths::wasi_io_stream_resources) and threads them into call/read_value/ValEncoder so streams are recognized regardless of instantiation state.

  3. Created input streams carried the wrong resource type. The decode path pushed Box::new(AsyncReadStream::new(..)), whose inferred type was the concrete reader rather than DynInputStream, so try_into_resource_any tagged the handle with a type that failed the guest's own<input-stream> check (mismatched resource types). Annotating it as DynInputStream (as wasmtime-wasi itself does) fixes the handle type.

Example

Adds streams-io-component-server (a reactor component exporting count: func(data: input-stream) -> u64, reading the stream via wasi:io) and streams-io-tcp-client (a peer that invokes it with a native wRPC stream<u8>).

cargo build -p streams-io-component-server --target wasm32-wasip2
wrpc-wasmtime tcp serve ./target/wasm32-wasip2/debug/streams_io_component_server.wasm
# in another terminal
cargo run -p streams-io-tcp-client -- "[::1]:7761" "hello from a wRPC stream"
# => sent 24 bytes, server counted 24

The component reads its parameter purely through wasi:io/streams; the runtime bridges it to/from the wire stream<u8> transparently.

Notes

  • The // TODO: Implement a custom reader comment in codec.rs is left in place: the receive side still relies on the transport sub-reader reaching EOF rather than consuming the \0 stream terminator explicitly. This works for the TCP transport (verified for empty, single-byte, and multi-byte payloads) but the deeper terminator handling is left as follow-up.
  • The polyfill path passes an empty stream-resource set, preserving its existing host-type-only behavior (no functional change there).

@cargopete cargopete requested a review from rvolosatovs as a code owner May 30, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[rs] conflate wasi:io streams and stream<u8> in the runtime [rs] Set async paths in runtime-wasmtime

1 participant