dicej requested alexcrichton for a review on PR #13085.
dicej opened PR #13085 from dicej:fix-13040 to bytecodealliance:main:
This addresses #13040 for
wasmtime-wasi's WASIp2 implementation by callingtokio::task::yield_nowin<Deadline as Pollable>::readywhenselfis aDeadline::Past. Previously, it did nothing, which had the effect of starving other pollables that rely on yielding control tomioin order to progress, e.g. when the guest polls with a zero timeout in a busy loop.This also addresses that issue for
wasmtime'sthread.yieldimplementation by modifying thepoll_untilevent loop inconcurrent.rsto yield to the executor prior to executing any low-priority tasks. This works becausethread.yieldoperates by queueing a low-priority task to resume the calling fiber just prior to suspending it.Note that, because the
wasmtimecrate does not depend on Tokio, we can't directly calltokio::task::yield_now. Instead, we return aFuturewhich, when polled for the first time, wakes theContext'sWakerand returnsPoll::Pending; then it returnsPoll::Ready(())for subsequent polls. That's enough to ensuremiohas a chance to run when usingtokio. Once we have a mechanism to allow the embedder to configure a custom yield function, we'll be able to use that instead.Fixes #13040
<!--
Please make sure you include the following information:
If this work has been discussed elsewhere, please include a link to that
conversation. If it was discussed in an issue, just mention "issue #...".Explain why this change is needed. If the details are in an issue already,
this can be brief.Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.htmlPlease ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->
dicej requested wasmtime-wasi-reviewers for a review on PR #13085.
dicej requested wasmtime-core-reviewers for a review on PR #13085.
dicej updated PR #13085.
alexcrichton submitted PR review.
alexcrichton created PR review comment:
I might recommend expanding this comment a bit to explain how what this is doing is a bit of a hack. The goal is that when a guest does a
pollwith a zero timeout in a loop we should eventually make our way back to Tokio, but if this function does nothing then that won't ever happen and we'll spin forever in the guest without actually yielding to Tokio. This is a best-attempt workaround to detect more-or-less this situation but is not really a full-fidelity fix per-se since there's other theoretical ways to busy loop too, but it's probably good enough for now nonetheless.
alexcrichton created PR review comment:
Mind expanding on this a bit too like above? Here's less of a hack insofar as low priority here is "the guest explicitly yielded" and so we're faithfully respecting that request to go all the way back up to the host at least once. This could perhaps be expanded with docs along the lines of future optimizations too such as periodically yielding back rather than unconditionally yielding back or something like that, but we've got no need for that just yet.
alexcrichton created PR review comment:
Could this be moved to a method on
StoreOpaque? For example:impl StoreOpaque { async fn yield_now(&mut self) { // ... } }
alexcrichton created PR review comment:
How come this is now taking a bool parameter? That seems like a subtle change from before which is unrelated to the issue-at-hand. Not that I think it's wrong, just curious.
Also, I find this method signature pretty confusing personally with bools-in-bools-out and subtle interactions between them. Another aspect is that I wouldn't exepct this to return
trueif nothing is actually pulled off the low priority queue either.This function is only called once in this file, so could this be inlined directly? I think it'd be easier to follow all the
boolsif they're just local variables and logic to read.
alexcrichton created PR review comment:
Also, could you double check we're not doing this sort of yield elsewhere in Wasmtime? I feel like we probably do this in at least one more location that can be deduplicated with this ideally.
dicej created PR review comment:
Yeah, the bool parameter is partly an artifact of studying the code looking for problems while I debugged an issue that ended up being caused elsewhere. While I was doing that, I realized we were executing items from the low-priority queue rather than looping around to see if
ConcurrentState::futureshad more ready futures first, which felt like a priority inversion.Anyway, you're right that it's not needed in this PR; I'll break it out, and I'll inline what remains.
dicej submitted PR review.
github-actions[bot] added the label wasmtime:api on PR #13085.
github-actions[bot] added the label wasi on PR #13085.
dicej updated PR #13085.
dicej updated PR #13085.
dicej submitted PR review.
dicej created PR review comment:
There was identical code in
runtime/vm/async_yield.rs, which I've consolidated into the newStoreOpaque::yield_nowfunction. There's one awkward case, though:StoreOpaque::do_gcusesunwrap_gc_store_mutto get exclusive access to theGcStore, which means we can't use the containingStoreOpaqueduring the call toGcStore::gc. For now, I'm just passing a freestanding function toGcStore::gcfor it to use when it needs to yield. Once we have e.g. aConfig::yield_fnfield of typeOption<fn() -> Box<dyn Future<Output = ()> + Send + Sync>>, we'll be able to pass in a closure which closes over that.
dicej updated PR #13085.
dicej updated PR #13085.
alexcrichton submitted PR review.
alexcrichton created PR review comment:
Accidental update?
dicej submitted PR review.
dicej created PR review comment:
oof, thanks for catching that; probably a rebase hiccup. I'll fix it.
dicej updated PR #13085.
dicej has enabled auto merge for PR #13085.
dicej added PR #13085 yield to executor when polling with zero-duration wait to the merge queue.
dicej merged PR #13085.
dicej removed PR #13085 yield to executor when polling with zero-duration wait from the merge queue.
Last updated: May 03 2026 at 22:13 UTC