Byte-Naut requested pchickey for a review on PR #14362.
Byte-Naut requested wasmtime-core-reviewers for a review on PR #14362.
Byte-Naut opened PR #14362 from Byte-Naut:issue-14291-concurrent-resource-drop to bytecodealliance:main:
Closes #14291.
Motivation
With the component-model-async APIs there is currently no supported way to
drop a guest-exportedResourceAnywhen the host only has anAccessor,
e.g. insideStore::run_concurrentor a spawnedAccessorTask.
resource_dropandresource_drop_asyncboth need aStoreContextMutthat
lives across the destructor call, which cannot escape anAccessor::with
closure, and blocking inside the closure would stall the store's event loop.
@SilverMira ran into this in #9946 and @alexcrichton suggested a
resource_drop_concurrentmethod as the missing piece.Changes
Add
ResourceAny::resource_drop_concurrent(self, accessor: impl AsAccessor)
behindcomponent-model-async.Make
StoreOpaque::queue_taskpub(crate)so the new method can reuse the
existingWorkerFunctionpath. No changes to the queue or event loop.Update the
resource_dropdocs to list all three variants and when each
applies, which should also help with the documentation concerns raised in
#9946.Tests: the
ApiStyle::Concurrentarm ofthread_index_via_resource_drop
now exercises the new method, and a new
resource_drop_concurrent_from_accessor_tasktest reproduces the
AccessorTaskscenario from the issue.Design
The drop is queued as a
WorkerFunctionon the store's worker fiber using
the same oneshot-plus-StoreTokenpattern asstart_instance. The
Accessor::withclosure only creates the channel and enqueues the work, so
the store borrow ends before the caller awaits the result. Running the
destructor on the worker fiber keeps guest code off the embedder's stack.
resource_drop_implis shared unchanged, so resource table handling,
may_enterchecks, and sync-call bookkeeping are identical to the existing
sync and async variants.Two behaviours worth calling out for review:
Destructor errors are sent back to the caller over the channel rather than
surfacing as an event loop error.Once the future has been polled and the drop is queued, dropping the future
does not cancel the drop; the queued work still runs and the send failure
is ignored.Both are documented on the method. Happy to adjust either if a different
policy is preferred.Testing
The test component's destructor calls
thread.indexand traps if it is zero,
so the tests check that the destructor actually runs in a guest thread
context, not just that no error is returned.cargo test --test all resource_dropLocally: fmt, clippy,
cargo doc --all-featureswith-D warnings, and the
component_modeltest suite all pass.Out of scope
bindgen-generated resource bindings currently only exposeresource_drop
andresource_drop_async. Adding a_concurrentvariant there seemed better
as a follow-up once the core API shape is settled, but I can fold it in here
if preferred.This PR was written with assistance from Claude (Anthropic); I reviewed and
tested the changes and take responsibility for them, per the Bytecode
Alliance AI Tool Use Policy.
Byte-Naut edited PR #14362:
Closes #14291.
Motivation
With the component-model-async APIs there is currently no supported way to drop a guest-exported
ResourceAnywhen the host only has anAccessor, e.g. insideStore::run_concurrentor a spawnedAccessorTask.resource_dropandresource_drop_asyncboth need aStoreContextMutthat lives across the destructor call, which cannot escape anAccessor::withclosure, and blocking inside the closure would stall the store's event loop. @SilverMira ran into this in #9946 and @alexcrichton suggested aresource_drop_concurrentmethod as the missing piece.Changes
- Add
ResourceAny::resource_drop_concurrent(self, accessor: impl AsAccessor)behindcomponent-model-async.- Make
StoreOpaque::queue_taskpub(crate)so the new method can reuse the existingWorkerFunctionpath. No changes to the queue or event loop.- Update the
resource_dropdocs to list all three variants and when each applies, which should also help with the documentation concerns raised in #9946.- Tests: the
ApiStyle::Concurrentarm ofthread_index_via_resource_dropnow exercises the new method, and a newresource_drop_concurrent_from_accessor_tasktest reproduces theAccessorTaskscenario from the issue.Design
The drop is queued as a
WorkerFunctionon the store's worker fiber using the same oneshot-plus-StoreTokenpattern asstart_instance. TheAccessor::withclosure only creates the channel and enqueues the work, so the store borrow ends before the caller awaits the result. Running the destructor on the worker fiber keeps guest code off the embedder's stack.resource_drop_implis shared unchanged, so resource table handling,may_enterchecks, and sync-call bookkeeping are identical to the existing sync and async variants.Two behaviours worth calling out for review:
- Destructor errors are sent back to the caller over the channel rather than surfacing as an event loop error.
- Once the future has been polled and the drop is queued, dropping the future does not cancel the drop; the queued work still runs and the send failure is ignored.
Both are documented on the method. Happy to adjust either if a different policy is preferred.
Testing
The test component's destructor calls
thread.indexand traps if it is zero, so the tests check that the destructor actually runs in a guest thread context, not just that no error is returned.cargo test --test all resource_dropLocally: fmt, clippy,
cargo doc --all-featureswith-D warnings, and thecomponent_modeltest suite all pass.Out of scope
bindgen-generated resource bindings currently only exposeresource_dropandresource_drop_async. Adding a_concurrentvariant there seemed better as a follow-up once the core API shape is settled, but I can fold it in here if preferred.This PR was written with assistance from Claude (Anthropic); I reviewed and tested the changes and take responsibility for them, per the Bytecode Alliance AI Tool Use Policy.
Byte-Naut edited PR #14362:
Closes #14291.
Motivation
With the component-model-async APIs there is currently no supported way to drop a guest-exported
ResourceAnywhen the host only has anAccessor, e.g. insideStore::run_concurrentor a spawnedAccessorTask.resource_dropandresource_drop_asyncboth need aStoreContextMutthat lives across the destructor call, which cannot escape anAccessor::withclosure, and blocking inside the closure would stall the store's event loop. @SilverMira ran into this in #9946 and @alexcrichton suggested aresource_drop_concurrentmethod as the missing piece.Changes
- Add
ResourceAny::resource_drop_concurrent(self, accessor: impl AsAccessor)behindcomponent-model-async.- Make
StoreOpaque::queue_taskpub(crate)so the new method can reuse the existingWorkerFunctionpath. No changes to the queue or event loop.- Update the
resource_dropdocs to list all three variants and when each applies, which should also help with the documentation concerns raised in #9946.- Tests: the
ApiStyle::Concurrentarm ofthread_index_via_resource_dropnow exercises the new method, and a newresource_drop_concurrent_from_accessor_tasktest reproduces theAccessorTaskscenario from the issue.Design
The drop is queued as a
WorkerFunctionon the store's worker fiber using the same oneshot-plus-StoreTokenpattern asstart_instance. TheAccessor::withclosure only creates the channel and enqueues the work, so the store borrow ends before the caller awaits the result. Running the destructor on the worker fiber keeps guest code off the embedder's stack.resource_drop_implis shared unchanged, so resource table handling,may_enterchecks, and sync-call bookkeeping are identical to the existing sync and async variants.Two behaviours worth calling out for review:
- Destructor errors are sent back to the caller over the channel rather than surfacing as an event loop error.
- Once the future has been polled and the drop is queued, dropping the future does not cancel the drop; the queued work still runs and the send failure is ignored.
Both are documented on the method. Happy to adjust either if a different policy is preferred.
Testing
The test component's destructor calls
thread.indexand traps if it is zero, so the tests check that the destructor actually runs in a guest thread context, not just that no error is returned.cargo test --test all resource_dropLocally: fmt, clippy,
cargo doc --all-featureswith-D warnings, and thecomponent_modeltest suite all pass.Out of scope
bindgen-generated resource bindings currently only exposeresource_dropandresource_drop_async. Adding a_concurrentvariant there seemed better as a follow-up once the core API shape is settled, but I can fold it in here if preferred.
Byte-Naut edited PR #14362:
Closes #14291.
Motivation
With the component-model-async APIs there is currently no supported way to drop a guest-exported
ResourceAnywhen the host only has anAccessor, e.g. insideStore::run_concurrentor a spawnedAccessorTask.resource_dropandresource_drop_asyncboth need aStoreContextMutthat lives across the destructor call, which cannot escape anAccessor::withclosure, and blocking inside the closure would stall the store's event loop. @SilverMira ran into this in #9946 and @alexcrichton suggested aresource_drop_concurrentmethod as the missing piece.Changes
- Add
ResourceAny::resource_drop_concurrent(self, accessor: impl AsAccessor)behindcomponent-model-async.- Make
StoreOpaque::queue_taskpub(crate)so the new method can reuse the existingWorkerFunctionpath. No changes to the queue or event loop.- Update the
resource_dropdocs to list all three variants and when each applies, which should also help with the documentation concerns raised in #9946.- Tests: the
ApiStyle::Concurrentarm ofthread_index_via_resource_dropnow exercises the new method, and a newresource_drop_concurrent_from_accessor_tasktest reproduces theAccessorTaskscenario from the issue.Design
The drop is queued as a
WorkerFunctionon the store's worker fiber using the same oneshot-plus-StoreTokenpattern asstart_instance. TheAccessor::withclosure only creates the channel and enqueues the work, so the store borrow ends before the caller awaits the result. Running the destructor on the worker fiber keeps guest code off the embedder's stack.resource_drop_implis shared unchanged, so resource table handling,may_enterchecks, and sync-call bookkeeping are identical to the existing sync and async variants.Two behaviours worth calling out for review:
- Destructor errors are sent back to the caller over the channel rather than surfacing as an event loop error.
- Once the future has been polled and the drop is queued, dropping the future does not cancel the drop; the queued work still runs and the send failure is ignored.
Both are documented on the method. Happy to adjust either if a different policy is preferred.
Testing
The test component's destructor calls
thread.indexand traps if it is zero, so the tests check that the destructor actually runs in a guest thread context, not just that no error is returned.cargo test --test all resource_dropLocally: fmt, clippy,
cargo doc --all-featureswith-D warnings, and thecomponent_modeltest suite all pass.Out of scope
bindgen-generated resource bindings currently only exposeresource_dropandresource_drop_async. Adding a_concurrentvariant there seemed better as a follow-up once the core API shape is settled, but I can fold it in here if preferred.Developed with assistance from Claude (Anthropic). Per the Bytecode Alliance AI Tool Use Policy, I'm the author and accountable for this change.
github-actions[bot] added the label wasmtime:api on PR #14362.
Last updated: Sep 20 2026 at 18:08 UTC