alexcrichton assigned dicej to issue #14618.
alexcrichton opened issue #14618:
This test:
;;! component_model_async = true ;;! component_model_threading = true ;; $A makes an async-lowered call to $J's callback-lifted "f", so $A's fiber is ;; parked waiting for "f" to suspend or return. "f" then makes a sync-lowered ;; call to $K's sync-typed "g", which spawns a thread and yields to it. When ;; that thread exits, $K has a sync-typed call in progress, so control must ;; switch back to "g"'s thread rather than to $A. $A must only be resumed once ;; "f" returns. (component (component $K (core module $libc (table (export "t") 1 funcref)) (core instance $libc (instantiate $libc)) (core type $ft (func (param i32))) (core func $thread.new-indirect (canon thread.new-indirect $ft (core table $libc "t"))) (core func $thread.yield-then-resume (canon thread.yield-then-resume)) (core module $m (import "" "thread.new-indirect" (func $new (param i32 i32) (result i32))) (import "" "thread.yield-then-resume" (func $ytr (param i32) (result i32))) (import "" "t" (table 1 funcref)) (func $start (param i32)) (elem (i32.const 0) func $start) (func (export "g") (result i32) (drop (call $ytr (call $new (i32.const 0) (i32.const 0)))) (i32.const 42)) ) (core instance $i (instantiate $m (with "" (instance (export "thread.new-indirect" (func $thread.new-indirect)) (export "thread.yield-then-resume" (func $thread.yield-then-resume)) (export "t" (table $libc "t")))))) (func (export "g") (result u32) (canon lift (core func $i "g"))) ) (component $J (import "g" (func $g (result u32))) (core func $g (canon lower (func $g))) (core func $task.return (canon task.return (result u32))) (core module $m (import "" "g" (func $g (result i32))) (import "" "task.return" (func $ret (param i32))) (func (export "f") (result i32) (call $ret (call $g)) (i32.const 0 (; EXIT ;))) (func (export "cb") (param i32 i32 i32) (result i32) unreachable) ) (core instance $i (instantiate $m (with "" (instance (export "g" (func $g)) (export "task.return" (func $task.return)))))) (func (export "f") async (result u32) (canon lift (core func $i "f") async (callback (core func $i "cb")))) ) (component $A (import "f" (func $f async (result u32))) (core module $libc (memory (export "memory") 1)) (core instance $libc (instantiate $libc)) (core func $f (canon lower (func $f) async (memory (core memory $libc "memory")))) (core func $task.return (canon task.return (result u32))) (core module $m (import "" "f" (func $f (param i32) (result i32))) (import "" "task.return" (func $ret (param i32))) (import "libc" "memory" (memory 1)) (func (export "run") (result i32) ;; The subtask status should be RETURNED, and the result 42. (if (i32.ne (call $f (i32.const 0)) (i32.const 2 (; RETURNED ;))) (then unreachable)) (call $ret (i32.load (i32.const 0))) (i32.const 0 (; EXIT ;))) (func (export "cb") (param i32 i32 i32) (result i32) unreachable) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $f)) (export "task.return" (func $task.return)))) (with "libc" (instance $libc)))) (func (export "run") async (result u32) (canon lift (core func $i "run") async (callback (core func $i "cb")))) ) (instance $k (instantiate $K)) (instance $j (instantiate $J (with "g" (func $k "g")))) (instance $a (instantiate $A (with "f" (func $j "f")))) (func (export "run") (alias export $a "run")) ) (assert_return (invoke "run") (u32.const 42))fails with
$ cargo run wast foo.wast -W component-model-threading warning: linker stderr: ld: __eh_frame section too large (max 16MB) to encode dwarf unwind offsets in compact unwind table, performance of exception handling might be affected | = note: `#[warn(linker_messages)]` on by default warning: `wasmtime-cli` (bin "wasmtime") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s Running `target/debug/wasmtime wast foo.wast -W component-model-threading` thread 'main' (1310505) panicked at crates/wasmtime/src/runtime/bug.rs:63:13: BUG: switch item already set note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' (1310505) panicked at crates/wasmtime/src/runtime/fiber.rs:497:9: attempted to drop in-progress fiber without first calling `StoreFiber::dispose` stack backtrace: 0: 0x1051fcb44 - std[b23ccf54642dae0e]::backtrace_rs::backtrace::libunwind::trace at /rustc/2d8144b7880597b6e6d3dfd63a9a9efae3f533d3/library/std/src/../../backtrace/src/backtraca similar test
<details>
;;! component_model_async_stackful = true ;;! component_model_more_async_builtins = true ;; $B is called asynchronously by $C and, before it ever suspends, cancels ;; one of its own subtasks that is queued behind backpressure in $A, then ;; makes another async call. Per the spec the cancel returns ;; CANCELLED_BEFORE_STARTED, the next call is queued (STARTING), and ;; everything returns normally. Wasmtime: `BUG: switch item already set`. (component (component $A (core module $m (import "" "backpressure.inc" (func $bp-inc)) (import "" "task.return" (func $task-return)) (func (export "f") (call $bp-inc) (call $task-return))) (core func $bp-inc (canon backpressure.inc)) (core func $task-return (canon task.return)) (core instance $i (instantiate $m (with "" (instance (export "backpressure.inc" (func $bp-inc)) (export "task.return" (func $task-return)))))) (func (export "f") async (canon lift (core func $i "f") async))) (component $B (import "f" (func $f async)) (core module $libc (memory (export "mem") 1)) (core instance $libc (instantiate $libc)) (core func $f (canon lower (func $f) async (memory (core memory $libc "mem")))) (core func $subtask-cancel (canon subtask.cancel async)) (core func $task-return (canon task.return)) (core module $m (import "" "f" (func $f (result i32))) (import "" "subtask.cancel" (func $subtask-cancel (param i32) (result i32))) (import "" "task.return" (func $task-return)) (func (export "run") ;; returns eagerly (RETURNED = 2), leaving backpressure enabled in $A (if (i32.ne (call $f) (i32.const 2)) (then unreachable)) ;; queued behind backpressure: subtask 1, STARTING = 0 (if (i32.ne (call $f) (i32.const 0x10)) (then unreachable)) ;; CANCELLED_BEFORE_STARTED = 3 (if (i32.ne (call $subtask-cancel (i32.const 1)) (i32.const 3)) (then unreachable)) ;; queued again: subtask 2, STARTING (if (i32.ne (call $f) (i32.const 0x20)) (then unreachable)) (call $task-return))) (core instance $i (instantiate $m (with "" (instance (export "f" (func $f)) (export "subtask.cancel" (func $subtask-cancel)) (export "task.return" (func $task-return)))))) (func (export "run") async (canon lift (core func $i "run") async))) (component $C (import "run" (func $run async)) (core module $libc (memory (export "mem") 1)) (core instance $libc (instantiate $libc)) (core func $run (canon lower (func $run) async (memory (core memory $libc "mem")))) (core func $task-return (canon task.return)) (core module $m (import "" "run" (func $run (result i32))) (import "" "task.return" (func $task-return)) (func (export "run") ;; $B returns without blocking (if (i32.ne (call $run) (i32.const 2)) (then unreachable)) (call $task-return))) (core instance $i (instantiate $m (with "" (instance (export "run" (func $run)) (export "task.return" (func $task-return)))))) (func (export "run") async (canon lift (core func $i "run") async))) (instance $a (instantiate $A)) (instance $b (instantiate $B (with "f" (func $a "f")))) (instance $c (instantiate $C (with "run" (func $b "run")))) (export "run" (func $c "run"))) (assert_return (invoke "run"))</details>
fails with:
$ cargo run wast foo.wast -W component-model-async-stackful,component-model-more-async-builtins warning: linker stderr: ld: __eh_frame section too large (max 16MB) to encode dwarf unwind offsets in compact unwind table, performance of exception handling might be affected | = note: `#[warn(linker_messages)]` on by default warning: `wasmtime-cli` (bin "wasmtime") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s Running `target/debug/wasmtime wast foo.wast -W component-model-async-stackful,component-model-more-async-builtins` thread 'main' (1312064) panicked at crates/wasmtime/src/runtime/bug.rs:63:13: BUG: switch item already set note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceIf useful, an LLM summary is:
<details>
cleanup_thread(crates/wasmtime/src/runtime/component/concurrent.rs)
starts withtake_next_switch_item(). That moves the store-wide
next_switch_item(normally "resume the caller") intoswitch_item
("run this next"). Ifswitch_itemis already set,set_switch_item
hitsbail_bug!("switch item already set"). The rejected work item holds
a fiber, which is dropped withoutStoreFiber::dispose.Three paths reach it:
- The host drops an unstarted
call_asyncfuture. Default
configuration. Cancelling the call goes through
cancel_guest_subtask_without_lowered_parameters→cleanup_thread.
The cancelled thread never ran, sonext_switch_itemisn't its own:
it belongs to whatever the event loop was about to do. When a guest
makes an async call to another gue
[message truncated]
alexcrichton added the wasm-proposal:component-model-async label to Issue #14618.
alexcrichton added the wasm-proposal:component-threading label to Issue #14618.
alexcrichton added the bug label to Issue #14618.
dicej closed issue #14618:
This test:
;;! component_model_async = true ;;! component_model_threading = true ;; $A makes an async-lowered call to $J's callback-lifted "f", so $A's fiber is ;; parked waiting for "f" to suspend or return. "f" then makes a sync-lowered ;; call to $K's sync-typed "g", which spawns a thread and yields to it. When ;; that thread exits, $K has a sync-typed call in progress, so control must ;; switch back to "g"'s thread rather than to $A. $A must only be resumed once ;; "f" returns. (component (component $K (core module $libc (table (export "t") 1 funcref)) (core instance $libc (instantiate $libc)) (core type $ft (func (param i32))) (core func $thread.new-indirect (canon thread.new-indirect $ft (core table $libc "t"))) (core func $thread.yield-then-resume (canon thread.yield-then-resume)) (core module $m (import "" "thread.new-indirect" (func $new (param i32 i32) (result i32))) (import "" "thread.yield-then-resume" (func $ytr (param i32) (result i32))) (import "" "t" (table 1 funcref)) (func $start (param i32)) (elem (i32.const 0) func $start) (func (export "g") (result i32) (drop (call $ytr (call $new (i32.const 0) (i32.const 0)))) (i32.const 42)) ) (core instance $i (instantiate $m (with "" (instance (export "thread.new-indirect" (func $thread.new-indirect)) (export "thread.yield-then-resume" (func $thread.yield-then-resume)) (export "t" (table $libc "t")))))) (func (export "g") (result u32) (canon lift (core func $i "g"))) ) (component $J (import "g" (func $g (result u32))) (core func $g (canon lower (func $g))) (core func $task.return (canon task.return (result u32))) (core module $m (import "" "g" (func $g (result i32))) (import "" "task.return" (func $ret (param i32))) (func (export "f") (result i32) (call $ret (call $g)) (i32.const 0 (; EXIT ;))) (func (export "cb") (param i32 i32 i32) (result i32) unreachable) ) (core instance $i (instantiate $m (with "" (instance (export "g" (func $g)) (export "task.return" (func $task.return)))))) (func (export "f") async (result u32) (canon lift (core func $i "f") async (callback (core func $i "cb")))) ) (component $A (import "f" (func $f async (result u32))) (core module $libc (memory (export "memory") 1)) (core instance $libc (instantiate $libc)) (core func $f (canon lower (func $f) async (memory (core memory $libc "memory")))) (core func $task.return (canon task.return (result u32))) (core module $m (import "" "f" (func $f (param i32) (result i32))) (import "" "task.return" (func $ret (param i32))) (import "libc" "memory" (memory 1)) (func (export "run") (result i32) ;; The subtask status should be RETURNED, and the result 42. (if (i32.ne (call $f (i32.const 0)) (i32.const 2 (; RETURNED ;))) (then unreachable)) (call $ret (i32.load (i32.const 0))) (i32.const 0 (; EXIT ;))) (func (export "cb") (param i32 i32 i32) (result i32) unreachable) ) (core instance $i (instantiate $m (with "" (instance (export "f" (func $f)) (export "task.return" (func $task.return)))) (with "libc" (instance $libc)))) (func (export "run") async (result u32) (canon lift (core func $i "run") async (callback (core func $i "cb")))) ) (instance $k (instantiate $K)) (instance $j (instantiate $J (with "g" (func $k "g")))) (instance $a (instantiate $A (with "f" (func $j "f")))) (func (export "run") (alias export $a "run")) ) (assert_return (invoke "run") (u32.const 42))fails with
$ cargo run wast foo.wast -W component-model-threading warning: linker stderr: ld: __eh_frame section too large (max 16MB) to encode dwarf unwind offsets in compact unwind table, performance of exception handling might be affected | = note: `#[warn(linker_messages)]` on by default warning: `wasmtime-cli` (bin "wasmtime") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s Running `target/debug/wasmtime wast foo.wast -W component-model-threading` thread 'main' (1310505) panicked at crates/wasmtime/src/runtime/bug.rs:63:13: BUG: switch item already set note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' (1310505) panicked at crates/wasmtime/src/runtime/fiber.rs:497:9: attempted to drop in-progress fiber without first calling `StoreFiber::dispose` stack backtrace: 0: 0x1051fcb44 - std[b23ccf54642dae0e]::backtrace_rs::backtrace::libunwind::trace at /rustc/2d8144b7880597b6e6d3dfd63a9a9efae3f533d3/library/std/src/../../backtrace/src/backtraca similar test
<details>
;;! component_model_async_stackful = true ;;! component_model_more_async_builtins = true ;; $B is called asynchronously by $C and, before it ever suspends, cancels ;; one of its own subtasks that is queued behind backpressure in $A, then ;; makes another async call. Per the spec the cancel returns ;; CANCELLED_BEFORE_STARTED, the next call is queued (STARTING), and ;; everything returns normally. Wasmtime: `BUG: switch item already set`. (component (component $A (core module $m (import "" "backpressure.inc" (func $bp-inc)) (import "" "task.return" (func $task-return)) (func (export "f") (call $bp-inc) (call $task-return))) (core func $bp-inc (canon backpressure.inc)) (core func $task-return (canon task.return)) (core instance $i (instantiate $m (with "" (instance (export "backpressure.inc" (func $bp-inc)) (export "task.return" (func $task-return)))))) (func (export "f") async (canon lift (core func $i "f") async))) (component $B (import "f" (func $f async)) (core module $libc (memory (export "mem") 1)) (core instance $libc (instantiate $libc)) (core func $f (canon lower (func $f) async (memory (core memory $libc "mem")))) (core func $subtask-cancel (canon subtask.cancel async)) (core func $task-return (canon task.return)) (core module $m (import "" "f" (func $f (result i32))) (import "" "subtask.cancel" (func $subtask-cancel (param i32) (result i32))) (import "" "task.return" (func $task-return)) (func (export "run") ;; returns eagerly (RETURNED = 2), leaving backpressure enabled in $A (if (i32.ne (call $f) (i32.const 2)) (then unreachable)) ;; queued behind backpressure: subtask 1, STARTING = 0 (if (i32.ne (call $f) (i32.const 0x10)) (then unreachable)) ;; CANCELLED_BEFORE_STARTED = 3 (if (i32.ne (call $subtask-cancel (i32.const 1)) (i32.const 3)) (then unreachable)) ;; queued again: subtask 2, STARTING (if (i32.ne (call $f) (i32.const 0x20)) (then unreachable)) (call $task-return))) (core instance $i (instantiate $m (with "" (instance (export "f" (func $f)) (export "subtask.cancel" (func $subtask-cancel)) (export "task.return" (func $task-return)))))) (func (export "run") async (canon lift (core func $i "run") async))) (component $C (import "run" (func $run async)) (core module $libc (memory (export "mem") 1)) (core instance $libc (instantiate $libc)) (core func $run (canon lower (func $run) async (memory (core memory $libc "mem")))) (core func $task-return (canon task.return)) (core module $m (import "" "run" (func $run (result i32))) (import "" "task.return" (func $task-return)) (func (export "run") ;; $B returns without blocking (if (i32.ne (call $run) (i32.const 2)) (then unreachable)) (call $task-return))) (core instance $i (instantiate $m (with "" (instance (export "run" (func $run)) (export "task.return" (func $task-return)))))) (func (export "run") async (canon lift (core func $i "run") async))) (instance $a (instantiate $A)) (instance $b (instantiate $B (with "f" (func $a "f")))) (instance $c (instantiate $C (with "run" (func $b "run")))) (export "run" (func $c "run"))) (assert_return (invoke "run"))</details>
fails with:
$ cargo run wast foo.wast -W component-model-async-stackful,component-model-more-async-builtins warning: linker stderr: ld: __eh_frame section too large (max 16MB) to encode dwarf unwind offsets in compact unwind table, performance of exception handling might be affected | = note: `#[warn(linker_messages)]` on by default warning: `wasmtime-cli` (bin "wasmtime") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s Running `target/debug/wasmtime wast foo.wast -W component-model-async-stackful,component-model-more-async-builtins` thread 'main' (1312064) panicked at crates/wasmtime/src/runtime/bug.rs:63:13: BUG: switch item already set note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceIf useful, an LLM summary is:
<details>
cleanup_thread(crates/wasmtime/src/runtime/component/concurrent.rs)
starts withtake_next_switch_item(). That moves the store-wide
next_switch_item(normally "resume the caller") intoswitch_item
("run this next"). Ifswitch_itemis already set,set_switch_item
hitsbail_bug!("switch item already set"). The rejected work item holds
a fiber, which is dropped withoutStoreFiber::dispose.Three paths reach it:
- The host drops an unstarted
call_asyncfuture. Default
configuration. Cancelling the call goes through
cancel_guest_subtask_without_lowered_parameters→cleanup_thread.
The cancelled thread never ran, sonext_switch_itemisn't its own:
it belongs to whatever the event loop was about to do. When a guest
makes an async call to another guest, the
[message truncated]
dicej commented on issue #14618:
Fixed by #14624
Last updated: Oct 11 2026 at 04:10 UTC