alexcrichton assigned dicej to issue #12109.
alexcrichton opened issue #12109:
Given this input:
(component (type $s (stream u32)) (core module $libc (memory (export "m") 1)) (component $a (core instance $libc (instantiate $libc)) (core func $stream.read (canon stream.read $s async (memory $libc "m"))) (core module $m (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32))) (func (export "run") (param $r i32) (call $stream.read (local.get $r) (i32.const 0) (i32.const 0)) if unreachable end ) ) (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream.read)) )) )) (func (export "run") (param "s" $s) (canon lift (core func $i "run"))) ) (component $b (import "a" (func $a (param "s" $s))) (core instance $libc (instantiate $libc)) (core func $stream.new (canon stream.new $s)) (core func $stream.write (canon stream.write $s async (memory $libc "m"))) (core func $a (canon lower (func $a))) (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.poll (canon waitable-set.poll (memory $libc "m"))) (core func $waitable.join (canon waitable.join)) (core module $m (import "" "stream.new" (func $stream.new (result i64))) (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32))) (import "" "a" (func $a (param i32))) (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.poll" (func $waitable-set.poll (param i32 i32) (result i32))) (import "" "waitable.join" (func $waitable.join (param i32 i32))) (import "" "m" (memory 1)) (func (export "run") (local $tmp i64) (local $r i32) (local $w i32) (local $s i32) (local $rc 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.write (local.get $w) (i32.const 0) (i32.const 1)) i32.const -1 i32.ne if unreachable end (call $a (local.get $r)) (local.set $s (call $waitable-set.new)) (call $waitable.join (local.get $w) (local.get $s)) (local.set $rc (call $waitable-set.poll (local.get $s) (i32.const 100))) (i32.ne (local.get $rc) (i32.const 3)) ;; EVENT_STREAM_WRITE if unreachable end ) ) (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream.new)) (export "stream.write" (func $stream.write)) (export "a" (func $a)) (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.poll" (func $waitable-set.poll)) (export "waitable.join" (func $waitable.join)) (export "m" (memory $libc "m")) )) )) (func (export "run") async (canon lift (core func $i "run"))) ) (instance $a (instantiate $a)) (instance $b (instantiate $b (with "a" (func $a "run")))) (export "run" (func $b "run")) ) (assert_return (invoke "run"))I see:
$ WASMTIME_BACKTRACE_DETAILS=1 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:95 1: error while executing at wasm backtrace: 0: 0x41e - wasm-function[6] at ./foo.wast:71:24 2: wasm trap: wasm `unreachable` instruction executedReading over this documentation, namely the definitions of
readandwrite, I believe that a zero-length read should unblock a nonzero-length write (or, well, any write). The only special case is that a zero-length-read with a zero-length-write unblocks the write only (not the read).cc @lukewagner on this too
alexcrichton added the wasm-proposal:component-model-async label to Issue #12109.
lukewagner commented on issue #12109:
There may be some wording or code that needs to be improved, but I think (and re-reading it just now, CanonicalABI.md#stream-state seems to match): a zero-length {read or write} should never unblock a non-zero-length {read or write}. Specifically, if the writer arrives first and blocks, then the read will be guarded here by
if dst_buffer.remain() > 0(wheredst_buffer.remain()is the length passed tostream.read) and thuspending_on_copyis not called (which would have woken the writer) whileon_copy_doneis called (which makesstream.readcomplete eagerly).
alexcrichton commented on issue #12109:
Ah ok I think I confused myself as to what I was reading. So it's expected that zero-length-things never wakeup non-zero-length-things, but two zero-length-things always have the writer wake up?
lukewagner commented on issue #12109:
Yep!
alexcrichton closed issue #12109:
Given this input:
(component (type $s (stream u32)) (core module $libc (memory (export "m") 1)) (component $a (core instance $libc (instantiate $libc)) (core func $stream.read (canon stream.read $s async (memory $libc "m"))) (core module $m (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32))) (func (export "run") (param $r i32) (call $stream.read (local.get $r) (i32.const 0) (i32.const 0)) if unreachable end ) ) (core instance $i (instantiate $m (with "" (instance (export "stream.read" (func $stream.read)) )) )) (func (export "run") (param "s" $s) (canon lift (core func $i "run"))) ) (component $b (import "a" (func $a (param "s" $s))) (core instance $libc (instantiate $libc)) (core func $stream.new (canon stream.new $s)) (core func $stream.write (canon stream.write $s async (memory $libc "m"))) (core func $a (canon lower (func $a))) (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.poll (canon waitable-set.poll (memory $libc "m"))) (core func $waitable.join (canon waitable.join)) (core module $m (import "" "stream.new" (func $stream.new (result i64))) (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32))) (import "" "a" (func $a (param i32))) (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.poll" (func $waitable-set.poll (param i32 i32) (result i32))) (import "" "waitable.join" (func $waitable.join (param i32 i32))) (import "" "m" (memory 1)) (func (export "run") (local $tmp i64) (local $r i32) (local $w i32) (local $s i32) (local $rc 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.write (local.get $w) (i32.const 0) (i32.const 1)) i32.const -1 i32.ne if unreachable end (call $a (local.get $r)) (local.set $s (call $waitable-set.new)) (call $waitable.join (local.get $w) (local.get $s)) (local.set $rc (call $waitable-set.poll (local.get $s) (i32.const 100))) (i32.ne (local.get $rc) (i32.const 3)) ;; EVENT_STREAM_WRITE if unreachable end ) ) (core instance $i (instantiate $m (with "" (instance (export "stream.new" (func $stream.new)) (export "stream.write" (func $stream.write)) (export "a" (func $a)) (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.poll" (func $waitable-set.poll)) (export "waitable.join" (func $waitable.join)) (export "m" (memory $libc "m")) )) )) (func (export "run") async (canon lift (core func $i "run"))) ) (instance $a (instantiate $a)) (instance $b (instantiate $b (with "a" (func $a "run")))) (export "run" (func $b "run")) ) (assert_return (invoke "run"))I see:
$ WASMTIME_BACKTRACE_DETAILS=1 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:95 1: error while executing at wasm backtrace: 0: 0x41e - wasm-function[6] at ./foo.wast:71:24 2: wasm trap: wasm `unreachable` instruction executedReading over this documentation, namely the definitions of
readandwrite, I believe that a zero-length read should unblock a nonzero-length write (or, well, any write). The only special case is that a zero-length-read with a zero-length-write unblocks the write only (not the read).cc @lukewagner on this too
alexcrichton commented on issue #12109:
Ok cool sounds good! In that case I'll close this and work on adjusting my fuzzer-to-be...
Last updated: Dec 06 2025 at 06:05 UTC