EFEXP opened issue #14284:
Feature
Add some way for the host to temporarily make a Component Model async guest task ineligible to enter or resume guest execution.
The motivating case is similar to Cloudflare Durable Objects'
blockConcurrencyWhile: if async exports A and B are running concurrently in the same component instance, A should be able to enter an exclusive section during which B executes no guest code, while A and host-side async work continue to make progress.It could look like:
pause_task(task: GuestTaskId) resume_task(task: GuestTaskId)or some equivalent scheduler-level abstraction.
This seems related to #11896, which discusses pausing tasks in the concurrent scheduler while allowing a privileged debugger task to continue. I'm mainly interested in whether there is already an intended abstraction for this kind of scheduler control.
Benefit
This would allow an embedder to implement exclusive execution regions across concurrent Component Model async calls without requiring cooperation from the guest.
The important distinction from instance backpressure is that the affected task may already have started. It might be waiting for an event or already queued for thread/fiber resumption, so the restriction needs to apply to both starting and re-entering guest code.
While a task is paused:
- unrelated guest tasks should continue to run;
- host futures and I/O should continue to make progress;
- queued work for the paused task should be retained;
- if the task is currently executing, the pause can take effect when it next returns to the scheduler.
This would also seem useful as a more general scheduler primitive for cases such as the debugger work discussed in #11896.
Implementation
Current
mainappears to already carryQualifiedThreadIdon bothResumeThreadandResumeFiber, so all guest-executing work items seem to have enough identity to associate them with a task.One possible implementation would be to add a task-level scheduler eligibility check covering guest-call start/event delivery as well as thread/fiber resumption, and retain ineligible work until the task becomes runnable again.
I'm not sure whether the natural unit should be exactly one
GuestTaskId, or a root export task together with work descended from it. For the motivating use case, root-task semantics would probably be more useful.I'd be interested in whether this matches the scheduler direction already envisioned for #11896, or whether Wasmtime would model this differently.
Alternatives
One alternative is to implement the exclusion in the guest, for example by wrapping exports so that non-owning tasks return
Pendingwhile another task holds the exclusive section. That works, but requires cooperation from each guest language/runtime and therefore cannot be enforced by the host for arbitrary components.Another alternative is to delay host-originated completion futures. That can prevent some event deliveries, but does not cover already-queued starts, guest-to-guest events, or thread/fiber resumption.
Instance backpressure is also insufficient because it only controls starting calls, not resuming already-started task
alexcrichton added the wasm-proposal:component-model-async label to Issue #14284.
alexcrichton commented on issue #14284:
cc @lukewagner you might have thoughts on this feature request (and @dicej too) -- do y'all feel that this is something that'd be reasonable to add to the host-side runtime?
Personally the first question in my mind would be to push a bit harder on the alternative you mention in the issue here @EFEXP -- would guest-side handling work for your use case? I understand that it wouldn't work for arbitrary components, but from the perspective of not complicating the host runtime further it does seem like an attractive alternative.
lukewagner commented on issue #14284:
If I'm reading the documentation for
blockConcurrencyWhilecorrectly, it prevents newasyncexported functions from being called by the host, but it doesn't prevent any already-startedasynctasks from resuming execution. (This behavior also makes sense to me: if there were any pre-existingasynctasks already running andblockConcurrencyWhileprevented them from executing, this could easily cause intra-instance deadlocks.) If my reading is correct, then I thinkblockConcurrencyWhilecould be implemented using the CM's currentbackpressure.{inc,dec}built-ins which has exactly this effect.
EFEXP commented on issue #14284:
@alexcrichton @lukewagner
Thanks for the feedback.Guest-side handling could work if we controlled the guest scheduler. The motivation for host-side support is to provide the same execution guarantees across components built with different languages and runtimes, without requiring scheduler changes in each of them.
The specific case is:
- Task B starts and waits for I/O.
- Task A enters an exclusive section and also waits for I/O.
- B’s I/O completes.
At step 3, we want to defer B’s re-entry into guest code until A leaves the section, while A and host-side I/O continue making progress. Preventing new export calls from starting would not cover this case.
Regarding
blockConcurrencyWhile, I checked workerd’s implementation. Its input gate explicitly includes subrequest responses, timers, and input streams, and its critical sections block delivery of events initiated outside the section:
- [Input gate semantics](https://github.com/cloudflare/workerd/blob/37d70e441156306dd20590f439ffa2560b0d1b18/src/workerd/io/io-gate.h#L12-L17)
- [Critical section semantics](https://github.com/cloudflare/workerd/blob/37d70e441156306dd20590f439ffa2560b0d1b18/src/workerd/io/io-gate.h#L175-L188)
I agree that dependencies on suspended tasks can cause deadlocks. The treatment of child tasks and reentrant calls would need careful definition.
Would a host-controlled scheduler eligibility hook fit Wasmtime’s intended direction, with the embedder responsible for the exclusion policy?
lukewagner commented on issue #14284:
I'm a bit skeptical that a host that treats the guest as a black-box and has no cooperation from the guest scheduler would be able to do a robust job implementing a critical section like you're describing. In the
workerdcode you link to: that code is effectively part of the JS guest scheduler and thus it can discriminate which events to let though (b/c they "part of" the critical section) and which to block; I think what you're asking for would be analogous to implementingblockConcurrencyWhileas part of a generic container runtime that runs an unmodified obliviousworkerd.That being said, one thing I believe you can do robustly from the host today is pause all execution of a whole
Store(which could contain a single root component instance as a host choice). So in your scenario, that would allow Step 2 to pause the wholeStoreuntil Task A's I/O was done, preventing Task B from running until Task A resumed. It's only once you want to attempt to do intra-component-instance task/thread discrimination that I think things get murky without the guest scheduler's participation.
EFEXP closed issue #14284:
Feature
Add some way for the host to temporarily make a Component Model async guest task ineligible to enter or resume guest execution.
The motivating case is similar to Cloudflare Durable Objects'
blockConcurrencyWhile: if async exports A and B are running concurrently in the same component instance, A should be able to enter an exclusive section during which B executes no guest code, while A and host-side async work continue to make progress.It could look like:
pause_task(task: GuestTaskId) resume_task(task: GuestTaskId)or some equivalent scheduler-level abstraction.
This seems related to #11896, which discusses pausing tasks in the concurrent scheduler while allowing a privileged debugger task to continue. I'm mainly interested in whether there is already an intended abstraction for this kind of scheduler control.
Benefit
This would allow an embedder to implement exclusive execution regions across concurrent Component Model async calls without requiring cooperation from the guest.
The important distinction from instance backpressure is that the affected task may already have started. It might be waiting for an event or already queued for thread/fiber resumption, so the restriction needs to apply to both starting and re-entering guest code.
While a task is paused:
- unrelated guest tasks should continue to run;
- host futures and I/O should continue to make progress;
- queued work for the paused task should be retained;
- if the task is currently executing, the pause can take effect when it next returns to the scheduler.
This would also seem useful as a more general scheduler primitive for cases such as the debugger work discussed in #11896.
Implementation
Current
mainappears to already carryQualifiedThreadIdon bothResumeThreadandResumeFiber, so all guest-executing work items seem to have enough identity to associate them with a task.One possible implementation would be to add a task-level scheduler eligibility check covering guest-call start/event delivery as well as thread/fiber resumption, and retain ineligible work until the task becomes runnable again.
I'm not sure whether the natural unit should be exactly one
GuestTaskId, or a root export task together with work descended from it. For the motivating use case, root-task semantics would probably be more useful.I'd be interested in whether this matches the scheduler direction already envisioned for #11896, or whether Wasmtime would model this differently.
Alternatives
One alternative is to implement the exclusion in the guest, for example by wrapping exports so that non-owning tasks return
Pendingwhile another task holds the exclusive section. That works, but requires cooperation from each guest language/runtime and therefore cannot be enforced by the host for arbitrary components.Another alternative is to delay host-originated completion futures. That can prevent some event deliveries, but does not cover already-queued starts, guest-to-guest events, or thread/fiber resumption.
Instance backpressure is also insufficient because it only controls starting calls, not resuming already-started task
EFEXP commented on issue #14284:
Thanks, Luke. I was able to get close to the desired behavior by pausing the whole Store using
Store::call_hook_async.
I agree with your point that distinguishing guest work inside a critical section from unrelated work requires cooperation from the guest scheduler.
I’ll explore coordinating the guest scheduler with the host to achieve workerd-like behavior without modifying Wasmtime itself.
Last updated: Sep 20 2026 at 18:08 UTC