Skip to content

Commit 63df06b

Browse files
authored
Support custom yield behavior for epoch interrupts (#10671)
* Support custom yield behavior for epoch interrupts As described in #10667, the yield future provided in-tree when a epoch callback cannot always trigger the specific behavior desired in async schedulers like tokio. This change introduces a new variant to the UpdateDeadline callback with the future implementing the desired task suspension behavior desired for yielding control within the async runtime. Note that this change does not allow for controlling this behavior for fuel yielding at this time. * YieldCustom review feedback updates
1 parent a045eaa commit 63df06b

File tree

2 files changed

+65
-5
lines changed

2 files changed

+65
-5
lines changed

crates/wasmtime/src/runtime/store.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ enum CallHookInner<T> {
252252

253253
/// What to do after returning from a callback when the engine epoch reaches
254254
/// the deadline for a Store during execution of a function using that store.
255+
#[non_exhaustive]
255256
pub enum UpdateDeadline {
256257
/// Extend the deadline by the specified number of ticks.
257258
Continue(u64),
@@ -260,6 +261,18 @@ pub enum UpdateDeadline {
260261
/// configured via [`Config::async_support`](crate::Config::async_support).
261262
#[cfg(feature = "async")]
262263
Yield(u64),
264+
/// Extend the deadline by the specified number of ticks after yielding to
265+
/// the async executor loop. This can only be used with an async [`Store`]
266+
/// configured via [`Config::async_support`](crate::Config::async_support).
267+
///
268+
/// The yield will be performed by the future provided; when using `tokio`
269+
/// it is recommended to provide [`tokio::task::yield_now`](https://docs.rs/tokio/latest/tokio/task/fn.yield_now.html)
270+
/// here.
271+
#[cfg(feature = "async")]
272+
YieldCustom(
273+
u64,
274+
::core::pin::Pin<Box<dyn ::core::future::Future<Output = ()> + Send>>,
275+
),
263276
}
264277

265278
// Forward methods on `StoreOpaque` to also being on `StoreInner<T>`
@@ -942,10 +955,10 @@ impl<T> Store<T> {
942955
/// add to the epoch deadline, as well as indicating what
943956
/// to do after the callback returns. If the [`Store`] is
944957
/// configured with async support, then the callback may return
945-
/// [`UpdateDeadline::Yield`] to yield to the async executor before
946-
/// updating the epoch deadline. Alternatively, the callback may
947-
/// return [`UpdateDeadline::Continue`] to update the epoch deadline
948-
/// immediately.
958+
/// [`UpdateDeadline::Yield`] or [`UpdateDeadline::YieldCustom`]
959+
/// to yield to the async executor before updating the epoch deadline.
960+
/// Alternatively, the callback may return [`UpdateDeadline::Continue`] to
961+
/// update the epoch deadline immediately.
949962
///
950963
/// This setting is intended to allow for coarse-grained
951964
/// interruption, but not a deterministic deadline of a fixed,
@@ -2104,7 +2117,6 @@ unsafe impl<T> crate::runtime::vm::VMStore for StoreInner<T> {
21042117
Some(callback) => callback((&mut *self).as_context_mut()).and_then(|update| {
21052118
let delta = match update {
21062119
UpdateDeadline::Continue(delta) => delta,
2107-
21082120
#[cfg(feature = "async")]
21092121
UpdateDeadline::Yield(delta) => {
21102122
assert!(
@@ -2116,6 +2128,27 @@ unsafe impl<T> crate::runtime::vm::VMStore for StoreInner<T> {
21162128
self.async_yield_impl()?;
21172129
delta
21182130
}
2131+
#[cfg(feature = "async")]
2132+
UpdateDeadline::YieldCustom(delta, future) => {
2133+
assert!(
2134+
self.async_support(),
2135+
"cannot use `UpdateDeadline::YieldCustom` without enabling async support in the config"
2136+
);
2137+
2138+
// When control returns, we have a `Result<()>` passed
2139+
// in from the host fiber. If this finished successfully then
2140+
// we were resumed normally via a `poll`, so keep going. If
2141+
// the future was dropped while we were yielded, then we need
2142+
// to clean up this fiber. Do so by raising a trap which will
2143+
// abort all wasm and get caught on the other side to clean
2144+
// things up.
2145+
unsafe {
2146+
self.async_cx()
2147+
.expect("attempted to pull async context during shutdown")
2148+
.block_on(future)?
2149+
}
2150+
delta
2151+
}
21192152
};
21202153

21212154
// Set a new deadline and return the new epoch deadline so

tests/all/epoch_interruption.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,33 @@ async fn epoch_callback_yield(config: &mut Config) {
381381
);
382382
}
383383

384+
#[wasmtime_test(with = "#[tokio::test]")]
385+
async fn epoch_callback_yield_custom(config: &mut Config) {
386+
assert_eq!(
387+
Some((1, 1)),
388+
run_and_count_yields_or_trap(
389+
config,
390+
"
391+
(module
392+
(import \"\" \"bump_epoch\" (func $bump))
393+
(func (export \"run\")
394+
call $bump ;; bump epoch
395+
call $subfunc) ;; call func; will notice new epoch and yield
396+
(func $subfunc))
397+
",
398+
1,
399+
InterruptMode::Callback(|mut cx| {
400+
let s = cx.data_mut();
401+
*s += 1;
402+
let fut = Box::pin(tokio::task::yield_now());
403+
Ok(UpdateDeadline::YieldCustom(1, fut))
404+
}),
405+
|_| {},
406+
)
407+
.await
408+
);
409+
}
410+
384411
#[wasmtime_test(with = "#[tokio::test]")]
385412
async fn epoch_callback_trap(config: &mut Config) {
386413
assert_eq!(

0 commit comments

Comments
 (0)