alexcrichton assigned dicej to issue #12108.
alexcrichton opened issue #12108:
For this test case:
(component (core module $libc (memory (export "m") 1)) (core instance $libc (instantiate $libc)) (type $s (stream u32)) (core func $stream.new (canon stream.new $s)) (core func $stream.read (canon stream.read $s async (memory $libc "m"))) (core func $stream.write (canon stream.write $s async (memory $libc "m"))) (core module $m (import "" "stream.new" (func $stream.new (result i64))) (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32))) (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32))) (func (export "run") (local $tmp i64) (local $r i32) (local $w i32) (local.set $tmp (call $stream.new)) (local.set $r (i32.wrap_i64 (local.get $tmp))) (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32)))) (call $stream.read (local.get $r) (i32.const 0) (i32.const 4)) i32.const -1 ;; BLOCKED i32.ne if unreachable end (call $stream.write (local.get $w) (i32.const 0) (i32.const 4)) drop ) ) (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream.new)) (export "stream.read" (func $stream.read)) (export "stream.write" (func $stream.write)) )) )) (func (export "run") (canon lift (core func $i "run"))) ) (assert_trap (invoke "run") "some message here")this yields:
$ cargo run wast foo.wast -W component-model-async Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s Running `target/x86_64-unknown-linux-gnu/debug/wasmtime wast foo.wast -W component-model-async` Error: failed to run script file 'foo.wast' Caused by: 0: failed directive on foo.wast:45 1: expected trap, got Component([])
alexcrichton added the wasm-proposal:component-model-async label to Issue #12108.
alexcrichton commented on issue #12108:
Same for futures:
(component (core module $libc (memory (export "m") 1)) (core instance $libc (instantiate $libc)) (type $s (future u32)) (core func $future.new (canon future.new $s)) (core func $future.read (canon future.read $s async (memory $libc "m"))) (core func $future.write (canon future.write $s async (memory $libc "m"))) (core module $m (import "" "future.new" (func $future.new (result i64))) (import "" "future.read" (func $future.read (param i32 i32) (result i32))) (import "" "future.write" (func $future.write (param i32 i32) (result i32))) (func (export "run") (local $tmp i64) (local $r i32) (local $w i32) (local.set $tmp (call $future.new)) (local.set $r (i32.wrap_i64 (local.get $tmp))) (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32)))) (call $future.read (local.get $r) (i32.const 0)) i32.const -1 ;; BLOCKED i32.ne if unreachable end (call $future.write (local.get $w) (i32.const 0)) drop ) ) (core instance $i (instantiate $m (with "" (instance (export "future.new" (func $future.new)) (export "future.read" (func $future.read)) (export "future.write" (func $future.write)) )) )) (func (export "run") (canon lift (core func $i "run"))) ) (assert_trap (invoke "run") "some message here")
dicej commented on issue #12108:
FYI, I've fixed this locally, but it turns out the
p3_http_middleware_host_to_hostis doing intra-componentstream<u8>reads and writes. Specifically, thep3_http_middlewarecomponent is creating a stream, wrapping it in a request, and passing that request to thep3_http_echocomponent. Then thep3_http_echocomponent consumes the request, wraps the stream in a response, and returns the response. Finally, thep3_http_middlewarecomponent consumes the response and reads from the stream, not realizing it's the same stream it created.I think we anticipated this kind of scenario, but it's not clear to me which component ought to change its behavior here. It seems unfair to make the
p3_http_echocomponent conservatively create an intermediate stream to avoid passing the original one back to the caller, and it's not clear to me howp3_http_middlewarecould be defensive about it either.@lukewagner do you have thoughts on this?
lukewagner commented on issue #12108:
The same-component trap is very much a "this will bite folks in unexpected and unfair ways" TODO to fix, so I think, unfortunately, the "right" fix is to have the echo server do an intermediate copy. FWIW, IIUC, once we get lazy-lowering, I think the same-component-instance case should mostly fall out for free (or with only modest effort) and so we could simply delete the trap.
dicej commented on issue #12108:
I'm remembering now when we last discussed this, and I guess the ideal solution would be for
wit_bindgen'sStream{Writer::write,Reader::read}to check for the intra-component case and switch to an in-memory channel. The tricky part is switching back and forth between the "intra" and "inter" states as the read handle is passed in and out of the component.
alexcrichton closed issue #12108:
For this test case:
(component (core module $libc (memory (export "m") 1)) (core instance $libc (instantiate $libc)) (type $s (stream u32)) (core func $stream.new (canon stream.new $s)) (core func $stream.read (canon stream.read $s async (memory $libc "m"))) (core func $stream.write (canon stream.write $s async (memory $libc "m"))) (core module $m (import "" "stream.new" (func $stream.new (result i64))) (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32))) (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32))) (func (export "run") (local $tmp i64) (local $r i32) (local $w i32) (local.set $tmp (call $stream.new)) (local.set $r (i32.wrap_i64 (local.get $tmp))) (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32)))) (call $stream.read (local.get $r) (i32.const 0) (i32.const 4)) i32.const -1 ;; BLOCKED i32.ne if unreachable end (call $stream.write (local.get $w) (i32.const 0) (i32.const 4)) drop ) ) (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream.new)) (export "stream.read" (func $stream.read)) (export "stream.write" (func $stream.write)) )) )) (func (export "run") (canon lift (core func $i "run"))) ) (assert_trap (invoke "run") "some message here")this yields:
$ cargo run wast foo.wast -W component-model-async Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s Running `target/x86_64-unknown-linux-gnu/debug/wasmtime wast foo.wast -W component-model-async` Error: failed to run script file 'foo.wast' Caused by: 0: failed directive on foo.wast:45 1: expected trap, got Component([])
Last updated: Dec 06 2025 at 07:03 UTC