From d0f6b82b89d10795c61d495b0dc55e850bcae23e Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 24 Feb 2026 14:11:49 -0800 Subject: [PATCH 1/2] Do not panic on host calls when CM concurrency support is disabled --- crates/wasmtime/src/runtime/component/concurrent.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index fc4cc2e8aa97..cc2b1537d7d2 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -1541,6 +1541,9 @@ impl StoreOpaque { /// optimized to avoid the full allocation of a `HostTask` in at least some /// situations. pub fn enter_host_call(&mut self) -> Result<()> { + if !self.concurrency_support() { + return Ok(()); + } let state = self.concurrent_state_mut(); let caller = state.unwrap_current_guest_thread(); let task = state.push(HostTask::new(caller, HostTaskState::CalleeStarted))?; @@ -1556,6 +1559,9 @@ impl StoreOpaque { /// the host isn't complete yet. In that situation the host task persists /// and will be cleaned up separately. pub fn exit_host_call(&mut self) -> Result<()> { + if !self.concurrency_support() { + return Ok(()); + } let task = self.concurrent_state_mut().unwrap_current_host_thread(); log::trace!("delete host task {task:?}"); let task = self.concurrent_state_mut().delete(task)?; From 1b13d60fc00a6d893539b7f88da8c0bce6db75fe Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 24 Feb 2026 15:14:49 -0800 Subject: [PATCH 2/2] review feedback --- .../src/runtime/component/concurrent.rs | 2 + tests/all/component_model/func.rs | 75 ++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index cc2b1537d7d2..ec1451bfd3aa 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -1542,6 +1542,7 @@ impl StoreOpaque { /// situations. pub fn enter_host_call(&mut self) -> Result<()> { if !self.concurrency_support() { + self.enter_call_not_concurrent(); return Ok(()); } let state = self.concurrent_state_mut(); @@ -1560,6 +1561,7 @@ impl StoreOpaque { /// and will be cleaned up separately. pub fn exit_host_call(&mut self) -> Result<()> { if !self.concurrency_support() { + self.exit_call_not_concurrent(); return Ok(()); } let task = self.concurrent_state_mut().unwrap_current_host_thread(); diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index cefdccc8538e..b74b1712b11c 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -1,7 +1,10 @@ #![cfg(not(miri))] use super::{ApiStyle, REALLOC_AND_FREE}; -use std::sync::Arc; +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering::SeqCst}, +}; use wasmtime::Result; use wasmtime::component::*; use wasmtime::{Config, Engine, Store, StoreContextMut, Trap}; @@ -3771,5 +3774,75 @@ async fn drop_call_async_future() -> Result<()> { _ = result; } } + + Ok(()) +} + +#[test] +fn host_call_with_concurrency_disabled() -> Result<()> { + let mut config = Config::default(); + config.concurrency_support(false); + + struct MyResource; + + let engine = Engine::new(&config)?; + let mut store = Store::new(&engine, ()); + let mut linker = Linker::<()>::new(&engine); + + linker + .root() + .resource("r", ResourceType::host::(), |_, _| Ok(()))?; + + let f_called = Arc::new(AtomicBool::new(false)); + linker.root().func_wrap("f", { + let f_called = f_called.clone(); + move |_ctx, _: (Resource,)| -> Result<()> { + f_called.store(true, SeqCst); + Ok(()) + } + })?; + + let component = Component::new( + &engine, + r#" + (component + (import "r" (type $r (sub resource))) + (import "f" (func $f (param "r" (borrow $r)))) + + (core func $f' (canon lower (func $f))) + (core func $drop (canon resource.drop $r)) + + (core module $m + (import "" "f" (func $f (param i32))) + (import "" "drop" (func $drop (param i32))) + (func (export "g") (param i32) + (call $f (local.get 0)) + (call $drop (local.get 0)) + ) + ) + + (core instance $i (instantiate $m (with + "" (instance + (export "f" (func $f')) + (export "drop" (func $drop)) + ) + ))) + + (func (export "g") (param "r" (borrow $r)) + (canon lift (core func $i "g")) + ) + ) + "# + .as_bytes(), + )?; + + let instance = linker.instantiate(&mut store, &component)?; + let g = instance.get_typed_func::<(&Resource,), ()>(&mut store, "g")?; + + let resource = Resource::new_own(100); + g.call(&mut store, (&resource,))?; + + assert!(f_called.load(SeqCst)); + Ok(()) }