Refactor borrow state tracking for async tasks #12550
Merged
alexcrichton merged 2 commits intobytecodealliance:mainfrom Feb 10, 2026
Merged
Refactor borrow state tracking for async tasks #12550alexcrichton merged 2 commits intobytecodealliance:mainfrom
alexcrichton merged 2 commits intobytecodealliance:mainfrom
Conversation
62d8213 to
44620e8
Compare
This commit is a somewhat deep refactoring of how the state of `borrow<T>` is managed for both the host and the guest with respect to async tasks. This additionally refactors how some async task management is done for host-called functions. The fundamental problem being tackled here is bytecodealliance#12510. In that issue it was discovered that the way `CallContext`, the borrow tracking mechanism in Wasmtime, is managed is incompatible with async tasks. Specifically the previous assumption of the scope being mutated for a borrow is somewhere on the call stack is no longer true. It's possible for an async task to be suspended, for example, and then a sibling task drops a borrow which should update the scope of the suspended task. There were a number of other small issues I noticed here and there which this PR additionally has tests for, all of which failed before this change and pass afterwards. The manner in which borrow state is manipulated is a pretty old part of the component model implementation dating back to the original implementation of resources. I decided to forgo any possible quick fix and have attempted to more deeply refactor and integrate async tasks into all of this infrastructure. A list of the changes made here are: * The `CallContexts` structure, a stack of `CallContext`, was removed. Tasks now directly store a `CallContext` which is the source of truth for borrow tracking for that call, and it does not move from this location. The store `CallContexts` is now deleted in favor of updating the `Option<ConcurrentState>` in the store to be an `enum` of either concurrent state or a stack. In this manner the old stack-based structure is still used sometimes, but it's impossible to reach when concurrency is enabled. * Entry to the host from guests now reliably pushes a `HostTask` into the store. Previously where a frame were always pushed into a `CallContext` a `HostTask` is pushed into the store. This is still expected to be a bit too expensive for cheap host calls, but it doesn't meaningfully change the performance profile of before. * The `resource_enter_call` and `resource_exit_call` libcalls have been removed. These are now folded into the `enter_sync_call` and `exit_sync_call` libcalls. Emission of these hooks has been updated accordingly. The concept of entering a call more generally has been removed. This is more formally known in the async world as a task starting, so the task creation is now responsible for the demarcation of entering a call. Additionally this means that the concept of exiting a call has somewhat gone away. Instead this method was renamed to `validate_scope_exit` which double-checks that a borrow-scope can be exited but doesn't actually remove the task. Task removal is deferred to preexisting mechanisms. * Management of a `GuestTask`'s previous `Option<CallContext>` field, for example taking/restoring and pushing/popping onto `CallContexts` is now all gone. All related code is outright deleted as the `GuestTask`'s now non-optional `CallContext` field is the source of truth. * The `ConcurrentState` structure now stores a `CurrentThread` enum instead of `Option<QualifiedThreadId>`. This represents how the currently executing thread could be a host thread, not just a guest thread, which is required for borrow-tracking. * `HostTask` creation in `poll_and_block` and `first_poll`, the two main entrypoints of async host tasks when called by the guest, is now externalized from these functions. Instead these functions assume that the currently running thread is already a `HostTask` of some kind. * In `poll_and_block` the host's result is no longer stored in the guest task but in the host task instead. Overall this enables the `*.wast` test for bytecodealliance#12510 to fix the original issue. This then adds new tests to ensure that cleanup of various constructs happens appropriately, such as cancelling a host task should clean up its associated resources. Additionally synchronously calling an async host task no longer leaks resources in a `Store` and should properly clean up everything. There is still more work to do in this area (e.g. bytecodealliance#12544) but that's going to be deferred to a future PR at this point. Closes bytecodealliance#12510 prtest:full
44620e8 to
8aeb10b
Compare
fitzgen
approved these changes
Feb 9, 2026
Member
fitzgen
left a comment
There was a problem hiding this comment.
I'm not too familiar with the CM async stuff, but this LGTM in general. If you want more detailed feedback, flag another reviewer :)
dicej
approved these changes
Feb 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit is a somewhat deep refactoring of how the state of
borrow<T>is managed for both the host and the guest with respect toasync tasks. This additionally refactors how some async task management
is done for host-called functions.
The fundamental problem being tackled here is #12510. In that issue it
was discovered that the way
CallContext, the borrow tracking mechanismin Wasmtime, is managed is incompatible with async tasks. Specifically
the previous assumption of the scope being mutated for a borrow is
somewhere on the call stack is no longer true. It's possible for an
async task to be suspended, for example, and then a sibling task drops a
borrow which should update the scope of the suspended task. There were a
number of other small issues I noticed here and there which this PR
additionally has tests for, all of which failed before this change and
pass afterwards.
The manner in which borrow state is manipulated is a pretty old part of
the component model implementation dating back to the original
implementation of resources. I decided to forgo any possible quick fix
and have attempted to more deeply refactor and integrate async tasks
into all of this infrastructure. A list of the changes made here are:
The
CallContextsstructure, a stack ofCallContext, was removed.Tasks now directly store a
CallContextwhich is the source of truthfor borrow tracking for that call, and it does not move from this
location. The store
CallContextsis now deleted in favor of updatingthe
Option<ConcurrentState>in the store to be anenumof eitherconcurrent state or a stack. In this manner the old stack-based
structure is still used sometimes, but it's impossible to reach when
concurrency is enabled.
Entry to the host from guests now reliably pushes a
HostTaskintothe store. Previously where a frame were always pushed into a
CallContextaHostTaskis pushed into the store. This is stillexpected to be a bit too expensive for cheap host calls, but it
doesn't meaningfully change the performance profile of before.
The
resource_enter_callandresource_exit_calllibcalls have beenremoved. These are now folded into the
enter_sync_callandexit_sync_calllibcalls. Emission of these hooks has been updatedaccordingly. The concept of entering a call more generally has been
removed. This is more formally known in the async world as a task
starting, so the task creation is now responsible for the demarcation
of entering a call. Additionally this means that the concept of
exiting a call has somewhat gone away. Instead this method was renamed
to
validate_scope_exitwhich double-checks that a borrow-scope canbe exited but doesn't actually remove the task. Task removal is
deferred to preexisting mechanisms.
Management of a
GuestTask's previousOption<CallContext>field,for example taking/restoring and pushing/popping onto
CallContextsis now all gone. All related code is outright deleted as the
GuestTask's now non-optionalCallContextfield is the source of truth.The
ConcurrentStatestructure now stores aCurrentThreadenuminstead of
Option<QualifiedThreadId>. This represents how thecurrently executing thread could be a host thread, not just a guest
thread, which is required for borrow-tracking.
HostTaskcreation inpoll_and_blockandfirst_poll, the two mainentrypoints of async host tasks when called by the guest, is now
externalized from these functions. Instead these functions assume that
the currently running thread is already a
HostTaskof some kind.In
poll_and_blockthe host's result is no longer stored in the guesttask but in the host task instead.
Overall this enables the
*.wasttest for #12510 to fix the originalissue. This then adds new tests to ensure that cleanup of various
constructs happens appropriately, such as cancelling a host task should
clean up its associated resources. Additionally synchronously calling an
async host task no longer leaks resources in a
Storeand shouldproperly clean up everything.
There is still more work to do in this area (e.g. #12544) but that's
going to be deferred to a future PR at this point.
Closes #12510
Depends on #12545, #12546, #12547, #12548, and #12549