Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow async yield from epoch interruption callback #6464

Merged
merged 2 commits into from
Jun 2, 2023

Conversation

jameysharp
Copy link
Contributor

When an epoch interruption deadline arrives, previously it was possible to yield to the async executor, or to invoke a callback on the wasm stack, but not both. This changes the API to allow callbacks to run and then request yielding to the async executor.

We discussed this in today's Wasmtime bi-weekly call and I believe this PR reflects the consensus from that discussion. But I'm open to bikeshedding the API.

I was tempted to revise the out-of-gas/fuel API to match the epoch interruption API as well, but decided not to do that in this PR.

@jameysharp jameysharp requested a review from a team as a code owner May 26, 2023 01:15
@jameysharp jameysharp requested review from pchickey and removed request for a team May 26, 2023 01:15
@jameysharp
Copy link
Contributor Author

My guess is if anybody has objections to this PR it'll probably be @alexcrichton or @fitzgen so maybe I should have picked one of you as reviewers instead of letting it auto-assign. Oh well.

@github-actions github-actions bot added the wasmtime:api Related to the API of the `wasmtime` crate itself label May 26, 2023
@github-actions
Copy link

Subscribe to Label Action

cc @peterhuene

This issue or pull request has been labeled: "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

  • peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

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

Mind adding some tests as well?

@@ -943,7 +941,7 @@ impl<T> Store<T> {
/// for an introduction to epoch-based interruption.
pub fn epoch_deadline_callback(
&mut self,
callback: impl FnMut(StoreContextMut<T>) -> Result<u64> + Send + Sync + 'static,
callback: impl FnMut(StoreContextMut<T>) -> DeadlineResult + Send + Sync + 'static,
Copy link
Member

Choose a reason for hiding this comment

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

From an ergonomic perspective could this remain Result<DeadlineResult>? (maybe with a bit of renaming)

That'll allow ? to be used for fallible operations and matches how traps are handled elsewhere in the embedding API where it's the Err of a Result

Copy link
Member

Choose a reason for hiding this comment

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

To be clear, this would remove the DeadlineResult::Trap variant right? Since that would become the Result::Err?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah that's what I'm thinking

None => Err(Trap::Interrupt.into()),
Some(callback) => {
let delta = match callback((&mut *self).as_context_mut()) {
// Note: If the callback returns an error, we don't call it again.
Copy link
Member

Choose a reason for hiding this comment

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

Is there any particular reason as to why? If something traps I could see it still being possible resuming other execution in the Store which may be unrelated which could require the same callback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I could tell this was the existing behavior, but it seemed non-obvious so I added a comment. I don't think there's a strong argument for behaving this way and I agree that it could be useful to preserve the callback.

Copy link
Member

Choose a reason for hiding this comment

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

Once something in a store traps, you really aren't supposed to use the store again.

Copy link
Member

Choose a reason for hiding this comment

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

Ah ok if this is preserving the existing behavior that's ok, but otherwise while you're here I think it might be good to "fix" this and put the callback back in place regardless of the result.

I agree that most of the time stores/instances/etc should be considered "poisoned" after trapping but that feels like a higher level concern which wouldn't affect a lower-level detail like this. Mostly in that we don't have anything official per-se about nullifying a store when it traps.

@@ -943,7 +941,7 @@ impl<T> Store<T> {
/// for an introduction to epoch-based interruption.
pub fn epoch_deadline_callback(
Copy link
Member

Choose a reason for hiding this comment

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

Can the documentation for this method be updated to talk about how returning Yield will panic if async_support is disabled?

Comment on lines 1955 to 1970
DeadlineResult::Yield(delta) => {
// Do the async yield. May return a trap if future was
// canceled while we're yielded.
self.async_yield_impl()?;
delta
}
Copy link
Member

Choose a reason for hiding this comment

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

In this case I believe that async_yield_impl will panic internally if it's not configured right but technically that method's panic should be allowable to be a debug_assert! or an unwrap_unchecked or something like that. I think it'd be good to have a dedicated assert here for the use case that this covers where Yield is returned but async isn't enabled or configured.

@alexcrichton alexcrichton requested review from alexcrichton and removed request for pchickey May 26, 2023 14:39
@@ -230,6 +231,18 @@ enum CallHookInner<T> {
Async(Box<dyn CallHookHandler<T> + Send + Sync>),
}

/// What to do after returning from a deadline callback.
pub enum DeadlineResult {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe

Suggested change
pub enum DeadlineResult {
pub enum DeadlineBehavior {

to make Result<DeadlineBehavior>?

Another option is to have this return a std::ops::ControlFlow where the break is an anyhow::Error and the continue is either a sync or async enum with a u64 payload. This would also allow for ? usage. But my experience with ControlFlow has not been super great.

@jameysharp jameysharp requested a review from a team as a code owner June 2, 2023 19:04
@jameysharp
Copy link
Contributor Author

I think I've addressed all your review comments.

I've also added a brief note to the release notes, though I'm not sure if it needs to be louder since it's an API break?

I'm hoping to get this into the next release, and I just realized today that's forking off on Monday. Sorry for not leaving a lot of time for review.

When an epoch interruption deadline arrives, previously it was possible
to yield to the async executor, or to invoke a callback on the wasm
stack, but not both. This changes the API to allow callbacks to run and
then request yielding to the async executor.
@alexcrichton alexcrichton added this pull request to the merge queue Jun 2, 2023
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jun 2, 2023
@jameysharp jameysharp enabled auto-merge June 2, 2023 19:43
@github-actions github-actions bot added wasmtime:c-api Issues pertaining to the C API. wasmtime:docs Issues related to Wasmtime's documentation labels Jun 2, 2023
@github-actions
Copy link

github-actions bot commented Jun 2, 2023

Subscribe to Label Action

cc @peterhuene

This issue or pull request has been labeled: "wasmtime:c-api", "wasmtime:docs"

Thus the following users have been cc'd because of the following labels:

  • peterhuene: wasmtime:c-api

To subscribe or unsubscribe from this label, edit the .github/subscribe-to-label.json configuration file.

Learn more.

@jameysharp jameysharp added this pull request to the merge queue Jun 2, 2023
Merged via the queue into bytecodealliance:main with commit 1d4686d Jun 2, 2023
@jameysharp jameysharp deleted the epoch-callback-yield branch June 2, 2023 21:04
salewski pushed a commit to salewski/wasmtime that referenced this pull request Jun 30, 2023
…6464)

When an epoch interruption deadline arrives, previously it was possible
to yield to the async executor, or to invoke a callback on the wasm
stack, but not both. This changes the API to allow callbacks to run and
then request yielding to the async executor.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wasmtime:api Related to the API of the `wasmtime` crate itself wasmtime:c-api Issues pertaining to the C API. wasmtime:docs Issues related to Wasmtime's documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants