alexcrichton opened issue #14562:
This input triggers a
bail_bug!onmain:(component (core module $shim (table (export "funcs") 1 1 funcref) (func (export "export") (result i32) (call_indirect (result i32) (i32.const 0)))) (core instance $shim (instantiate $shim)) (func $shim-export (result u32) (canon lift (core func $shim "export"))) (component $X (import "import" (func $import (result u32))) (core func $import (canon lower (func $import))) (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.drop (canon waitable-set.drop)) (core func $task.return (canon task.return)) (core module $m (import "" "import" (func $import (result i32))) (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.drop" (func $waitable-set.drop (param i32))) (import "" "task.return" (func $task.return)) (func (export "s") (result i32) (call $import)) (func (export "h") (result i32) (call $waitable-set.drop (call $waitable-set.new)) (i32.const 7)) (func (export "y") (result i32) (i32.const 1 (; YIELD ;))) (func (export "y-cb") (param i32 i32 i32) (result i32) (call $task.return) (i32.const 0 (; EXIT ;))) (func (export "z") (result i32) (call $task.return) (i32.const 0 (; EXIT ;)))) (core instance $i (instantiate $m (with "" (instance (export "import" (func $import)) (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.drop" (func $waitable-set.drop)) (export "task.return" (func $task.return)))))) (func (export "s") (result u32) (canon lift (core func $i "s"))) (func (export "h") async (result u32) (canon lift (core func $i "h"))) (func (export "y") async (canon lift (core func $i "y") async (callback (core func $i "y-cb")))) (func (export "z") async (canon lift (core func $i "z") async (callback (core func $i "y-cb"))))) (instance $x (instantiate $X (with "import" (func $shim-export)))) (core func $x-h (canon lower (func $x "h"))) (core module $donut (import "" "funcs" (table 1 1 funcref)) (import "" "h" (func $h (result i32))) (func $guest-export (result i32) (call $h)) (elem declare func $guest-export) (func $start (table.set (i32.const 0) (ref.func $guest-export))) (start $start)) (core instance $donut (instantiate $donut (with "" (instance (export "h" (func $x-h)) (export "funcs" (table $shim "funcs")))))) (component $D (import "x" (instance $x (export "s" (func (result u32))) (export "y" (func async)) (export "z" (func async)))) (core func $s (canon lower (func $x "s"))) (core func $y (canon lower (func $x "y") async)) (core func $z (canon lower (func $x "z") async)) (core func $task.return (canon task.return (result u32))) (core module $d (import "" "s" (func $s (result i32))) (import "" "y" (func $y (result i32))) (import "" "z" (func $z (result i32))) (import "" "task.return" (func $task.return (param i32))) (func (export "run") (result i32) (drop (call $y)) (drop (call $s)) (call $task.return (call $z)) (i32.const 0 (; EXIT ;))) (func (export "run-cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $d (instantiate $d (with "" (instance (export "s" (func $s)) (export "y" (func $y)) (export "z" (func $z)) (export "task.return" (func $task.return)))))) (func (export "run") async (result u32) (canon lift (core func $d "run") async (callback (core func $d "run-cb"))))) (instance $d (instantiate $D (with "x" (instance $x)))) (export "run" (func $d "run")) ) (assert_return (invoke "run") (u32.const 2 (; RETURNED ;)))as does this:
(component (component $F (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.drop (canon waitable-set.drop)) (core func $task.return (canon task.return)) (core module $m (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.drop" (func $waitable-set.drop (param i32))) (import "" "task.return" (func $task.return)) (func (export "g") (result i32) (call $waitable-set.drop (call $waitable-set.new)) (i32.const 7)) (func (export "k") (result i32) (call $task.return) (i32.const 0)) (func (export "cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.drop" (func $waitable-set.drop)) (export "task.return" (func $task.return)))))) (func (export "g") (result u32) (canon lift (core func $i "g"))) (func (export "k") async (canon lift (core func $i "k") async (callback (core func $i "cb"))))) (instance $f (instantiate $F)) (component $E (import "f" (instance $f (export "g" (func (result u32))) (export "k" (func async)))) (core func $g (canon lower (func $f "g"))) (core func $k (canon lower (func $f "k") async)) (core func $task.return (canon task.return)) (core module $m (import "" "g" (func $g (result i32))) (import "" "k" (func $k (result i32))) (import "" "task.return" (func $task.return)) (func (export "run") (result i32) (drop (call $g)) (drop (call $k)) (call $task.return) (i32.const 0)) (func (export "cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $i (instantiate $m (with "" (instance (export "g" (func $g)) (export "k" (func $k)) (export "task.return" (func $task.return)))))) (func (export "run") async (canon lift (core func $i "run") async (callback (core func $i "cb"))))) (instance $e (instantiate $E (with "f" (instance $f)))) (component $D (import "run" (func $run async)) (core func $run (canon lower (func $run) async)) (core func $task.return (canon task.return (result u32))) (core module $m (import "" "run" (func $run (result i32))) (import "" "task.return" (func $task.return (param i32))) (func (export "run") (result i32) (call $task.return (call $run)) (i32.const 0)) (func (export "cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $i (instantiate $m (with "" (instance (export "run" (func $run)) (export "task.return" (func $task.return)))))) (func (export "run") async (result u32) (canon lift (core func $i "run") async (callback (core func $i "cb"))))) (instance $d (instantiate $D (with "run" (func $e "run")))) (export "run" (func $d "run")) ) (assert_return (invoke "run") (u32.const 2))This is a smaller version which doesn't
bail_bug!but currently traps:(component (core module $shim (table (export "funcs") 1 1 funcref) (func (export "export") (result i32) (call_indirect (result i32) (i32.const 0)))) (core instance $shim (instantiate $shim)) (func $shim-export (result u32) (canon lift (core func $shim "export"))) (component $X (import "import" (func $import (result u32))) (core func $import (canon lower (func $import))) (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.drop (canon waitable-set.drop)) (core module $m (import "" "import" (func $import (result i32))) (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.drop" (func $waitable-set.drop (param i32))) (func (export "s") (result i32) (call $import)) (func (export "h") (result i32) ;; any intrinsic that forces materialization of the deferred ;; sync->sync thread chain (call $waitable-set.drop (call $waitable-set.new)) (i32.const 7))) (core instance $i (instantiate $m (with "" (instance (export "import" (func $import)) (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.drop" (func $waitable-set.drop)))))) (func (export "s") (result u32) (canon lift (core func $i "s"))) (func (export "h") async (result u32) (canon lift (core func $i "h")))) (instance $x (instantiate $X (with "import" (func $shim-export)))) (core func $x-h (canon lower (func $x "h"))) (core module $donut (import "" "funcs" (table 1 1 funcref)) (import "" "h" (func $h (result i32))) (func $guest-export (result i32) (call $h)) (elem declare func $guest-export) (func $start (table.set (i32.const 0) (ref.func $guest-export))) (start $start)) (core instance $donut (instantiate $donut (with "" (instance (export "h" (func $x-h)) (export "funcs" (table $shim "funcs")))))) (export "s" (func $x "s")) ) (assert_return (invoke "s") (u32.const 7))An LLM claims this fixes things:
<details>
diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 15d2bd7699..5e3b2dd0ba 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -1999,14 +1999,12 @@ impl StoreOpaque { log::trace!("exit sync-lifted call {instance:?}"); - if async_typed { - // If we're async-typed, returning control to our caller won't help - // resolve any outstanding sync-typed call which might be in - // progress, so we may need to switch or trap before exiting this - // thread: - self.switch_or_trap_if_may_not_suspend(instance)?; - } - + // Note that, unlike other places where a thread [message truncated]
alexcrichton added the wasm-proposal:component-model-async label to Issue #14562.
dicej assigned dicej to issue #14562.
alexcrichton commented on issue #14562:
In addition to the above tests, https://gist.github.com/alexcrichton/fb2af8436ac19825e7039b7e1049100c contains some more tests which I believe boil down to the same issue here. The behavior here ranges from "probably incorrect w.r.t." the spec to panics to crashes to blocked forever.
alexcrichton added the bug label to Issue #14562.
dicej commented on issue #14562:
I've got a patch that fixes all 6 of the tests posted here. Will open a PR shortly.
dicej closed issue #14562:
This input triggers a
bail_bug!onmain:(component (core module $shim (table (export "funcs") 1 1 funcref) (func (export "export") (result i32) (call_indirect (result i32) (i32.const 0)))) (core instance $shim (instantiate $shim)) (func $shim-export (result u32) (canon lift (core func $shim "export"))) (component $X (import "import" (func $import (result u32))) (core func $import (canon lower (func $import))) (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.drop (canon waitable-set.drop)) (core func $task.return (canon task.return)) (core module $m (import "" "import" (func $import (result i32))) (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.drop" (func $waitable-set.drop (param i32))) (import "" "task.return" (func $task.return)) (func (export "s") (result i32) (call $import)) (func (export "h") (result i32) (call $waitable-set.drop (call $waitable-set.new)) (i32.const 7)) (func (export "y") (result i32) (i32.const 1 (; YIELD ;))) (func (export "y-cb") (param i32 i32 i32) (result i32) (call $task.return) (i32.const 0 (; EXIT ;))) (func (export "z") (result i32) (call $task.return) (i32.const 0 (; EXIT ;)))) (core instance $i (instantiate $m (with "" (instance (export "import" (func $import)) (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.drop" (func $waitable-set.drop)) (export "task.return" (func $task.return)))))) (func (export "s") (result u32) (canon lift (core func $i "s"))) (func (export "h") async (result u32) (canon lift (core func $i "h"))) (func (export "y") async (canon lift (core func $i "y") async (callback (core func $i "y-cb")))) (func (export "z") async (canon lift (core func $i "z") async (callback (core func $i "y-cb"))))) (instance $x (instantiate $X (with "import" (func $shim-export)))) (core func $x-h (canon lower (func $x "h"))) (core module $donut (import "" "funcs" (table 1 1 funcref)) (import "" "h" (func $h (result i32))) (func $guest-export (result i32) (call $h)) (elem declare func $guest-export) (func $start (table.set (i32.const 0) (ref.func $guest-export))) (start $start)) (core instance $donut (instantiate $donut (with "" (instance (export "h" (func $x-h)) (export "funcs" (table $shim "funcs")))))) (component $D (import "x" (instance $x (export "s" (func (result u32))) (export "y" (func async)) (export "z" (func async)))) (core func $s (canon lower (func $x "s"))) (core func $y (canon lower (func $x "y") async)) (core func $z (canon lower (func $x "z") async)) (core func $task.return (canon task.return (result u32))) (core module $d (import "" "s" (func $s (result i32))) (import "" "y" (func $y (result i32))) (import "" "z" (func $z (result i32))) (import "" "task.return" (func $task.return (param i32))) (func (export "run") (result i32) (drop (call $y)) (drop (call $s)) (call $task.return (call $z)) (i32.const 0 (; EXIT ;))) (func (export "run-cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $d (instantiate $d (with "" (instance (export "s" (func $s)) (export "y" (func $y)) (export "z" (func $z)) (export "task.return" (func $task.return)))))) (func (export "run") async (result u32) (canon lift (core func $d "run") async (callback (core func $d "run-cb"))))) (instance $d (instantiate $D (with "x" (instance $x)))) (export "run" (func $d "run")) ) (assert_return (invoke "run") (u32.const 2 (; RETURNED ;)))as does this:
(component (component $F (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.drop (canon waitable-set.drop)) (core func $task.return (canon task.return)) (core module $m (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.drop" (func $waitable-set.drop (param i32))) (import "" "task.return" (func $task.return)) (func (export "g") (result i32) (call $waitable-set.drop (call $waitable-set.new)) (i32.const 7)) (func (export "k") (result i32) (call $task.return) (i32.const 0)) (func (export "cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $i (instantiate $m (with "" (instance (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.drop" (func $waitable-set.drop)) (export "task.return" (func $task.return)))))) (func (export "g") (result u32) (canon lift (core func $i "g"))) (func (export "k") async (canon lift (core func $i "k") async (callback (core func $i "cb"))))) (instance $f (instantiate $F)) (component $E (import "f" (instance $f (export "g" (func (result u32))) (export "k" (func async)))) (core func $g (canon lower (func $f "g"))) (core func $k (canon lower (func $f "k") async)) (core func $task.return (canon task.return)) (core module $m (import "" "g" (func $g (result i32))) (import "" "k" (func $k (result i32))) (import "" "task.return" (func $task.return)) (func (export "run") (result i32) (drop (call $g)) (drop (call $k)) (call $task.return) (i32.const 0)) (func (export "cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $i (instantiate $m (with "" (instance (export "g" (func $g)) (export "k" (func $k)) (export "task.return" (func $task.return)))))) (func (export "run") async (canon lift (core func $i "run") async (callback (core func $i "cb"))))) (instance $e (instantiate $E (with "f" (instance $f)))) (component $D (import "run" (func $run async)) (core func $run (canon lower (func $run) async)) (core func $task.return (canon task.return (result u32))) (core module $m (import "" "run" (func $run (result i32))) (import "" "task.return" (func $task.return (param i32))) (func (export "run") (result i32) (call $task.return (call $run)) (i32.const 0)) (func (export "cb") (param i32 i32 i32) (result i32) unreachable)) (core instance $i (instantiate $m (with "" (instance (export "run" (func $run)) (export "task.return" (func $task.return)))))) (func (export "run") async (result u32) (canon lift (core func $i "run") async (callback (core func $i "cb"))))) (instance $d (instantiate $D (with "run" (func $e "run")))) (export "run" (func $d "run")) ) (assert_return (invoke "run") (u32.const 2))This is a smaller version which doesn't
bail_bug!but currently traps:(component (core module $shim (table (export "funcs") 1 1 funcref) (func (export "export") (result i32) (call_indirect (result i32) (i32.const 0)))) (core instance $shim (instantiate $shim)) (func $shim-export (result u32) (canon lift (core func $shim "export"))) (component $X (import "import" (func $import (result u32))) (core func $import (canon lower (func $import))) (core func $waitable-set.new (canon waitable-set.new)) (core func $waitable-set.drop (canon waitable-set.drop)) (core module $m (import "" "import" (func $import (result i32))) (import "" "waitable-set.new" (func $waitable-set.new (result i32))) (import "" "waitable-set.drop" (func $waitable-set.drop (param i32))) (func (export "s") (result i32) (call $import)) (func (export "h") (result i32) ;; any intrinsic that forces materialization of the deferred ;; sync->sync thread chain (call $waitable-set.drop (call $waitable-set.new)) (i32.const 7))) (core instance $i (instantiate $m (with "" (instance (export "import" (func $import)) (export "waitable-set.new" (func $waitable-set.new)) (export "waitable-set.drop" (func $waitable-set.drop)))))) (func (export "s") (result u32) (canon lift (core func $i "s"))) (func (export "h") async (result u32) (canon lift (core func $i "h")))) (instance $x (instantiate $X (with "import" (func $shim-export)))) (core func $x-h (canon lower (func $x "h"))) (core module $donut (import "" "funcs" (table 1 1 funcref)) (import "" "h" (func $h (result i32))) (func $guest-export (result i32) (call $h)) (elem declare func $guest-export) (func $start (table.set (i32.const 0) (ref.func $guest-export))) (start $start)) (core instance $donut (instantiate $donut (with "" (instance (export "h" (func $x-h)) (export "funcs" (table $shim "funcs")))))) (export "s" (func $x "s")) ) (assert_return (invoke "s") (u32.const 7))An LLM claims this fixes things:
<details>
diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 15d2bd7699..5e3b2dd0ba 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -1999,14 +1999,12 @@ impl StoreOpaque { log::trace!("exit sync-lifted call {instance:?}"); - if async_typed { - // If we're async-typed, returning control to our caller won't help - // resolve any outstanding sync-typed call which might be in - // progress, so we may need to switch or trap before exiting this - // thread: - self.switch_or_trap_if_may_not_suspend(instance)?; - } - + // Note that, unlike other places where a thread exits, [message truncated]
Last updated: Oct 11 2026 at 04:10 UTC