align multithreading and trap behavior with CM spec - #14146
Conversation
8506000 to
5d88b4c
Compare
alexcrichton
left a comment
There was a problem hiding this comment.
I'd like to review more of concurrent.rs but here's some initial thoughts. It's at the point where whenever I expand context on github it just sends me randomly elsewhere in this diff and I keep losing my spot in the otherwise big diff in concurrent.rs. I'm hoping my changes in dicej#7 which reduce the number of files changed helps with that...
|
I think this may also still have lingering management of |
6a0209e to
25af96d
Compare
29ac4ee to
b045d7e
Compare
|
@alexcrichton would you mind taking one more look at this? Aligning it with WebAssembly/component-model#705 required an overhaul and uncovered a few subtle divergences with the spec which I had to address. |
This updates Wasmtime's Component Model async and cooperative multithreading support to match the current specification, including: - Refined rules for trapping when a sync-typed function blocks. We now enforce this "lazily" rather than "eagerly", mwaning a sync-typed function is allowed to call an async-typed function or blocking intrinsic, and if it doesn't actually block, we won't trap. And if the call _does_ block, we will look for any eligible threads to run and run them until no such threads remain, only trapping if and when we still need to block and have no more threads to run. - Allow reentrance in all cases except when the instance has trapped. - Remove the previous "may block" bookkeeping at the task and root instance level, replacing it with (sub-)instance level tracking of whether any sync-typed function is running in that instance. - Run the event loop during start function calls since they are now allowed to call async-typed functions, create and resume threads, etc. I've also added some code to assert that the event loop is running when it is required. - Add `ConcurrentState::switch_item` for use when we need to run a specific work item at the next turn of the event loop, regardless of what's already in the `high_priority` queue. This is necessary because the spec is particular about which thread to switch to e.g. when calling a function or promoting a thread, and it won't allow us to run any other threads first. Note that this includes `test/component-model` submodule updates which haven't yet been merged to the main branch of the upstream repo, but should be merged soon. See WebAssembly/component-model#705 Fixes bytecodealliance#14117 Co-authored-by: Alex Crichton <[email protected]>
Accept an `unsafe` block which I believe is correct and still otherwise safe at the invocation site. The main hidden constraint now is that we can't transfer fibers to other non-store-bound-locations but that's effectively already true so shouldn't be too onerous to uphold.
- Ensure that subtask status updates are delivered promptly and deterministically according to the spec by using `ConcurrentState::switch_item` instead of `ConcurrentState::high_priority` - Fix reentrance scenarious involving `subtask.cancel` where the subtask tries to add itself to a waitable set before or after being canceled - Refine rules for switching-or-trapping on thread exit when the current instance has a sync-typed call in progress - Misc. bug fixes
Notably, this makes Wasmtime more aggressive about poisoning the store if an error happens when e.g. lifting a result (e.g. due to a misaligned pointer), and the tests have been updated accordingly.
b045d7e to
f511826
Compare
| Ok(()) | ||
| } | ||
|
|
||
| fn any_may_not_suspend(&mut self) -> Result<bool> { |
There was a problem hiding this comment.
This feels pretty bad as it's a linear search over everything in the concurrent table. Could this be managed with a counter of some kind instead or something like that?
There was a problem hiding this comment.
This is only used when we're about trap due to a deadlock situation, so I figured it isn't performance sensitive. With that in mind, do you still think it should be optimized?
| Some(WaitMode::Caller { .. }) => { | ||
| bail_bug!("unexpected `WaitMode::Caller` in wake_on_cancel set") |
There was a problem hiding this comment.
An observation on this: if this is actually hit it'll destroy the process due to one of the fields in the .. being a fiber that can't be dropped.
That being said this is actually present in a whole bunch of places throughout this file. In a myriad of locations where there's a fiber in scope there are instances of ? used, which only trigger on bugs, but if they were to occur would also destroy the fiber. I'm not entirely sure what to do about that, but we may want to track that as a general issue of something to improve at some point.
There was a problem hiding this comment.
I know I've said this before, but if you're for helping out at some point in the future this file really could benefit from being split up. It's kind of unreviewable in github's UI because if context is expanded anywhere then the UI jumps to some random position in the diff-of-all-files, meaning you can't actually keep your place in this file. That's almost surely a UI bug on github's side, but I think this file would benefit from being split up for a number of other reasons too.
| if let Some(Event::Subtask { | ||
| status: Status::Starting, | ||
| }) = &self.common(state)?.event | ||
| { | ||
| // `Status::Starting` means we can't invoke the | ||
| // callee yet due to e.g. backpressure, so go ahead | ||
| // and deliver the update now. | ||
| state.set_switch_item(item)?; | ||
| } else { | ||
| if state.get_mut(callee)?.switch_item.is_some() { | ||
| bail_bug!( | ||
| "`GuestTask::switch_item` is already `Some(_)` when we need \ | ||
| to deliver a subtask status update to the caller" | ||
| ); | ||
| } | ||
| state.get_mut(callee)?.switch_item = Some(item); | ||
| } |
There was a problem hiding this comment.
I think both of these branches boil down to set_switch_item, right?
There was a problem hiding this comment.
One is setting the switch item on the store, while the other is setting in on the task; i.e. they're two different fields. The latter is used to defer setting the store's switch item until the task either suspends or exits.
This updates Wasmtime's Component Model async and cooperative multithreading support to match the current specification, including:
Refined rules for trapping when a sync-typed function blocks. We now enforce this "lazily" rather than "eagerly", mwaning a sync-typed function is allowed to call an async-typed function or blocking intrinsic, and if it doesn't actually block, we won't trap. And if the call does block, we will look for any eligible threads to run and run them until no such threads remain, only trapping if and when we still need to block and have no more threads to run.
Ensure that the predicate for determining which threads can be run when a sync-typed function is executing in an instance matches the spec.
Remove the previous "may block" bookkeeping at the task and root instance level, replacing it with (sub-)instance level tracking of whether any sync-typed function is running in that instance.
Run the event loop during start function calls since they are now allowed to call async-typed functions, create and resume threads, etc.
Note that this includes
test/component-modelsubmodule updates which haven't yet been merged to the main branch of the upstream repo, but should be merged soon. See WebAssembly/component-model#696Fixes #14117