From 59fba6cc1e04f104b9e790b8c021c9b5d63a44ba Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 15 Jun 2026 08:41:23 -0700 Subject: [PATCH] Fix panic with component-model-threading This commit fixes an `assert!` tripping when using component-model-threads combined with `thread.yield cancellable`. --- .../src/runtime/component/concurrent.rs | 10 ++-- .../yield-cancellable.wast | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 tests/misc_testsuite/component-model-threading/yield-cancellable.wast diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 94ee8036c641..5ec4de15bb15 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -5294,12 +5294,12 @@ impl ConcurrentState { /// consuming the event if so. fn take_pending_cancellation(&mut self) -> Result { let thread = self.current_guest_thread()?; - if let Some(event) = self.get_mut(thread.task)?.event.take() { - assert!(matches!(event, Event::Cancelled)); - Ok(true) - } else { - Ok(false) + let task = self.get_mut(thread.task)?; + if let Some(Event::Cancelled) = task.event { + task.event.take(); + return Ok(true); } + Ok(false) } fn check_blocking_for(&mut self, task: TableId) -> Result<()> { diff --git a/tests/misc_testsuite/component-model-threading/yield-cancellable.wast b/tests/misc_testsuite/component-model-threading/yield-cancellable.wast new file mode 100644 index 000000000000..2f9227330041 --- /dev/null +++ b/tests/misc_testsuite/component-model-threading/yield-cancellable.wast @@ -0,0 +1,58 @@ +;;! component_model_async = true +;;! component_model_threading = true + +(component + (core module $libc + (table (export "t") 1 funcref)) + (core instance $libc (instantiate $libc)) + (core type $start-func-ty (func (param i32))) + (core func $thread.new-indirect + (canon thread.new-indirect $start-func-ty (table $libc "t"))) + (core func $thread.unsuspend (canon thread.unsuspend)) + (core func $thread.index (canon thread.index)) + (core func $thread.yield-cancellable (canon thread.yield cancellable)) + (core func $task.return (canon task.return)) + + (core module $m + (import "" "thread.new-indirect" (func $thread.new-indirect (param i32 i32) (result i32))) + (import "" "thread.unsuspend" (func $thread.unsuspend (param i32))) + (import "" "thread.index" (func $thread.index (result i32))) + (import "" "thread.yield-cancellable" (func $thread.yield-cancellable (result i32))) + (import "" "task.return" (func $task.return)) + (import "" "tbl" (table $tbl 1 funcref)) + + ;; entrypoint: create a thread, let it run, then yield ourselves. + (func (export "run") (result i32) + (local $tid i32) + (local.set $tid (call $thread.new-indirect (i32.const 0) (call $thread.index))) + (call $thread.unsuspend (local.get $tid)) + i32.const 1 ;; CALLBACK_CODE_YIELD + ) + + ;; thread: call `thread.yield-cancellable` and double-check it didn't pick + ;; up anything + (func $explicit-start (param $ctx i32) + (if (call $thread.yield-cancellable) + (then (unreachable))) + ) + (elem (table $tbl) (i32.const 0) func $explicit-start) + + ;; finishing up the entrypoint: just return + (func (export "cb") (param i32 i32 i32) (result i32) + call $task.return + i32.const 0 ;; CALLBACK_CODE_EXIT + ) + ) + + (core instance $i (instantiate $m (with "" (instance + (export "thread.new-indirect" (func $thread.new-indirect)) + (export "thread.unsuspend" (func $thread.unsuspend)) + (export "thread.index" (func $thread.index)) + (export "thread.yield-cancellable" (func $thread.yield-cancellable)) + (export "task.return" (func $task.return)) + (export "tbl" (table $libc "t")))))) + + (func (export "run") async + (canon lift (core func $i "run") async (callback (func $i "cb")))) +) +(assert_return (invoke "run"))