alexcrichton opened issue #11870:
This issue is similar to #11869 but I've separated it out to specifically deal with usage of
block_onwith respect towrap_asyncand its variants. Thewrap_asyncfunction has been a pillar of Wasmtime's async support historically but it's effectively entirely incompatible with component-model-async semantics. Notablywrap_async"locks" a store acrossawaitpoints meaning that it's not possible to progress other tasks at the same time. While this sort of means thatwrap_asyncis "just another blocking call" from the perspective of a component it has the notable consequence of makingrun_concurrentnot actually properly run things concurrently. Effectively this suffers from the same problems as #11869.Fixing this issue is expected to be difficult. Ideally we'd "just delete all calls and use
wrap_concurrentinstead", but that's a pretty large change to make. That also doesn't clearly translate to core wasm where there's no equivalent of concurrent calls at this time. A more localized fix would be to remove all of Wasmtime's usage ofwrap_async, document the pitfalls, and then move on. For examplewasmtime-wasiwould avoid usingwrap_asyncand would usewrap_concurrent(or an equivalent thereof) instead. This wouldn't actually fix WASIp1 APIs since there's no*_concurrentavailable, though.Regardless I wanted to write down this issue to have a place to discuss this and notably link to in the source/documentation about how this is a major limitation of
run_concurrenttoday.
alexcrichton added the wasm-proposal:component-model-async label to Issue #11870.
alexcrichton commented on issue #11870:
In talking with Joel/Luke some time ago, I wanted to write down thoughts relevant to this specifically as well. Notably the conclusion is that if wasm calls a synchronously-typed host function then it's not valid for the wasm to be reentered. This is entirely different for an async-typed host function but for synchronously-typed functions it's not valid for wasm to execute while the host is running.
This puts host calls in a similar situation as #11869 where when synchronous host functions are called we can't just start using
run_concurrent. Instead there needs to be some sort of infrastructure to prevent executing more wasm, probably like the async lock described in https://github.com/bytecodealliance/wasmtime/issues/11869#issuecomment-3832992306.This also showcases a glimmer of hope of being able to retain the function signature of
func_wrap_async(sort of). Assuming the function signature there is changed to restrict to&mut Tinstead ofStoreContextMut<T>, then that's sort of what happens under the hood. The host function gets to use&mut Tand the execution of the host function will block the guest. All further guest invocations will also be blocked, too. This is almost the same as Rust's borrowing rules, but the problem that remains is the fact that "on the other end", the hypotheticalrun_concurrentblock, the embedder still has aAccessor<T>which can temporarily grantStoreContextMut<T>. This gives unfettered access toT, even when it's being borrowed by a host function.This is not safe in Rust, so something needs to be done. Some ideas, none of which I like, are:
- Panic in
data_mut()anddata()if the data is borrowed in a host function at the same time. This means thatdata_mut()anddata()more-or-less couldn't be used withinrun_concurrent.- Change
func_wrap_asyncto have some sort of extra trait bound onT, such asT: Take. That could be represented asfn take(&mut self) -> Selfwhere theTis "moved" into the host function then put back when it's done. (probably with an overridablerestorefunction which by default overwritesself). This isn't great because it still means thatdata_mut()anddata()withinrun_concurrentbasically aren't helpful. While it's sound and doesn't panic the actual data is just an unuseful shell or husk.I personally don't want to force all embedders to switch to
run_concurrentjust yet, but this may just be inevitable. That correctly and accurately models the ownership situations, which is that fundamentally you only get access toTtemporarily, not for the duration of an async operation. It does sort of seem inevitable thatfunc_wrap_asyncneeds to become more restrictive one way or another. One end of the spectrum is deleting it outright, and the other end of the spectrum is having some sort of more restrictive trait bound which models some sort of common pattern of ownership.Anyway, that's the thoughts for now at least.
Last updated: Feb 24 2026 at 06:21 UTC