alexcrichton opened issue #13661:
This test:
(component (core module $libc (memory (export "m") 1)) (core instance $libc (instantiate $libc)) (type $f (future u8)) (core func $future.new (canon future.new $f)) (core func $future.drop-readable (canon future.drop-readable $f)) (core func $future.drop-writable (canon future.drop-writable $f)) (core module $m (import "" "m" (memory 1)) (import "" "future.new" (func $future.new (result i64))) (import "" "future.drop-readable" (func $future.drop-readable (param i32))) (import "" "future.drop-writable" (func $future.drop-writable (param 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.drop-readable (local.get $r)) (call $future.drop-writable (local.get $w)) ) ) (core instance $i (instantiate $m (with "" (instance (export "m" (memory $libc "m")) (export "future.new" (func $future.new)) (export "future.drop-readable" (func $future.drop-readable)) (export "future.drop-writable" (func $future.drop-writable)) )) )) (func (export "run") async (canon lift (core func $i "run"))) ) (assert_trap (invoke "run") "something here")creates a future, closed the read end, then closes the write end. I think this should be trap, but currently Wasmtime executes this.
cc @dicej - would you be able to take a look at this?
alexcrichton added the wasm-proposal:component-model-async label to Issue #13661.
cschanhniem commented on issue #13661:
This trap path is specified in the component model async ABI:
canon future.newreturns ani64packing both handles ((i64.or (i64.shl write-i32 32) read-i32)), and the writable end carries a "must write before close" obligation. The canonical ABI forfuture.drop-writableshould trap when the future state iscreated-but-unwritten— that's distinct from the existingtrap-if-sync-and-waitable-setchecks in the sync lowering paths.The test you linked from CM/#647 covers the sync path, but the async lowering in Wasmtime would hit a different code path in
crates/wasmtime/src/runtime/vm/component/futures.rs— theDropimpl orclose_writablemethod needs an additional state check on the writable end. The relevant state machine transitions are:Created → Written → ReadableClosed → WritableClosed. Dropping the writable end inCreatedshould emit the trap, not silently close.
dicej assigned dicej to issue #13661.
alexcrichton closed issue #13661:
This test:
(component (core module $libc (memory (export "m") 1)) (core instance $libc (instantiate $libc)) (type $f (future u8)) (core func $future.new (canon future.new $f)) (core func $future.drop-readable (canon future.drop-readable $f)) (core func $future.drop-writable (canon future.drop-writable $f)) (core module $m (import "" "m" (memory 1)) (import "" "future.new" (func $future.new (result i64))) (import "" "future.drop-readable" (func $future.drop-readable (param i32))) (import "" "future.drop-writable" (func $future.drop-writable (param 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.drop-readable (local.get $r)) (call $future.drop-writable (local.get $w)) ) ) (core instance $i (instantiate $m (with "" (instance (export "m" (memory $libc "m")) (export "future.new" (func $future.new)) (export "future.drop-readable" (func $future.drop-readable)) (export "future.drop-writable" (func $future.drop-writable)) )) )) (func (export "run") async (canon lift (core func $i "run"))) ) (assert_trap (invoke "run") "something here")creates a future, closed the read end, then closes the write end. I think this should be trap, but currently Wasmtime executes this.
cc @dicej - would you be able to take a look at this?
Last updated: Jul 29 2026 at 05:03 UTC