Skip to content

yield to executor when polling with zero-duration wait#13085

Merged
dicej merged 3 commits intobytecodealliance:mainfrom
dicej:fix-13040
Apr 14, 2026
Merged

yield to executor when polling with zero-duration wait#13085
dicej merged 3 commits intobytecodealliance:mainfrom
dicej:fix-13040

Conversation

@dicej
Copy link
Copy Markdown
Contributor

@dicej dicej commented Apr 13, 2026

This addresses #13040 for wasmtime-wasi's WASIp2 implementation by calling tokio::task::yield_now in <Deadline as Pollable>::ready when self is a Deadline::Past. Previously, it did nothing, which had the effect of starving other pollables that rely on yielding control to mio in order to progress, e.g. when the guest polls with a zero timeout in a busy loop.

This also addresses that issue for wasmtime's thread.yield implementation by modifying the poll_until event loop in concurrent.rs to yield to the executor prior to executing any low-priority tasks. This works because thread.yield operates by queueing a low-priority task to resume the calling fiber just prior to suspending it.

Note that, because the wasmtime crate does not depend on Tokio, we can't directly call tokio::task::yield_now. Instead, we return a Future which, when polled for the first time, wakes the Context's Waker and returns Poll::Pending; then it returns Poll::Ready(()) for subsequent polls. That's enough to ensure mio has a chance to run when using tokio. 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

@dicej dicej requested a review from alexcrichton April 13, 2026 23:17
@dicej dicej requested review from a team as code owners April 13, 2026 23:17
Comment thread crates/wasi/src/p2/host/clocks.rs
Comment thread crates/wasmtime/src/runtime/component/concurrent.rs
Comment on lines +5529 to +5546
async fn yield_to_executor(config: &Config) {
// TODO: Once `Config` has an optional `AsyncFn` field for yielding to the
// current async runtime (e.g. `tokio::task::yield_now`), use that if set;
// otherwise fall back to the runtime-agnostic code below.
_ = config;

let mut yielded = false;
future::poll_fn(move |cx| {
if yielded {
Poll::Ready(())
} else {
yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
})
.await;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be moved to a method on StoreOpaque? For example:

impl StoreOpaque {
    async fn yield_now(&mut self) {
        // ...
    }
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was identical code in runtime/vm/async_yield.rs, which I've consolidated into the new StoreOpaque::yield_now function. There's one awkward case, though: StoreOpaque::do_gc uses unwrap_gc_store_mut to get exclusive access to the GcStore, which means we can't use the containing StoreOpaque during the call to GcStore::gc. For now, I'm just passing a freestanding function to GcStore::gc for it to use when it needs to yield. Once we have e.g. a Config::yield_fn field of type Option<fn() -> Box<dyn Future<Output = ()> + Send + Sync>>, we'll be able to pass in a closure which closes over that.

Comment thread crates/wasmtime/src/runtime/component/concurrent.rs Outdated
@github-actions github-actions bot added wasi Issues pertaining to WASI wasmtime:api Related to the API of the `wasmtime` crate itself labels Apr 14, 2026
@dicej dicej force-pushed the fix-13040 branch 3 times, most recently from 5fdf95d to 59b5cc7 Compare April 14, 2026 15:48
Comment thread tests/spec_testsuite
dicej added 3 commits April 14, 2026 10:36
This addresses bytecodealliance#13040 for `wasmtime-wasi`'s WASIp2 implementation by calling
`tokio::task::yield_now` in `<Deadline as Pollable>::ready` when `self` is a
`Deadline::Past`.  Previously, it did nothing, which had the effect of starving
other pollables that rely on yielding control to `mio` in order to progress,
e.g. when the guest polls with a zero timeout in a busy loop.

This also addresses that issue for `wasmtime`'s `thread.yield` implementation by
modifying the `poll_until` event loop in `concurrent.rs` to yield to the
executor prior to executing any low-priority tasks.  This works because
`thread.yield` operates by queueing a low-priority task to resume the calling
fiber just prior to suspending it.

Note that, because the `wasmtime` crate does not depend on Tokio, we can't
directly call `tokio::task::yield_now`.  Instead, we return a `Future` which,
when polled for the first time, wakes the `Context`'s `Waker` and returns
`Poll::Pending`; then it returns `Poll::Ready(())` for subsequent polls.  That's
enough to ensure `mio` has a chance to run when using `tokio`.  Once we have a
mechanism to allow the embedder to configure a custom yield function, we'll be
able to use that instead.

Fixes bytecodealliance#13040
Now that zero-duration `monotonic_clock` waits yield to the executor, they
aren't ready immediately, so the assertions in this test are no longer
appropriate.
- Expand comments regarding why we yield to the runtime in `wasmtime` and `wasmtime-wasi`
- Move `yield_to_executor` to `StoreOpaque::yield_now` and remove `vm/async_yield` in favor of the new function
- Revert `take_low_priority` performance tweak (will open a separate PR for that)
- Inline `collect_work_itmes_to_run` for clarity
@dicej dicej enabled auto-merge April 14, 2026 16:38
@dicej dicej added this pull request to the merge queue Apr 14, 2026
Merged via the queue into bytecodealliance:main with commit 80be8d6 Apr 14, 2026
48 checks passed
@dicej dicej deleted the fix-13040 branch April 14, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wasi Issues pertaining to WASI wasmtime:api Related to the API of the `wasmtime` crate itself

Projects

None yet

Development

Successfully merging this pull request may close these issues.

polling with a zero timeout (p2) or waitable-set.polling (p3) can starve the async runtime

2 participants