jcmuller opened issue #14700:
Version: wasmtime 49.0.0, through the C API.
After a component call traps, the host cannot drop a
borrowhandle that it passed to that call.Steps
Instantiate the component below.
Itsrunexport takes aborrow<doc>and runsunreachable.Create a host resource
doc, and callrunwith it throughwasmtime_component_func_call.
The call traps.Drop the handle with
wasmtime_component_resource_any_drop.Expected: the drop succeeds.
Actual: the drop fails with
cannot remove owned resource while borrowed.
The entry stays in the host resource table until the Store drops.(component (import "test:host/doc" (instance $host (export "doc" (type $doc (sub resource))) (export "[method]doc.len" (func (param "self" (borrow $doc)) (result u64))))) (alias export $host "doc" (type $doc)) (alias export $host "[method]doc.len" (func $len)) (core module $libc (memory (export "mem") 1) (global $next (mut i32) (i32.const 2048)) (func (export "realloc") (param i32 i32 i32 i32) (result i32) (local $p i32) global.get $next i32.const 7 i32.add i32.const -8 i32.and local.tee $p local.get 3 i32.add global.set $next local.get $p)) (core instance $libc (instantiate $libc)) (alias core export $libc "mem" (core memory $mem)) (alias core export $libc "realloc" (core func $realloc)) (core func $len-core (canon lower (func $len))) (core func $drop-core (canon resource.drop $doc)) (core module $m (import "host" "len" (func $len (param i32) (result i64))) (import "host" "drop" (func $drop (param i32))) (import "libc" "mem" (memory 1)) (func (export "run") (param i32) unreachable)) (core instance $hi (export "len" (func $len-core)) (export "drop" (func $drop-core))) (core instance $li (export "mem" (memory $mem))) (core instance $i (instantiate $m (with "host" (instance $hi)) (with "libc" (instance $li)))) (func (export "run") (param "d" (borrow $doc)) (canon lift (core func $i "run") (memory $mem) (realloc $realloc))))The Go test that runs this is in my wasmtime-go fork:
https://github.com/jcmuller/wasmtime-go/blob/f15392a60bcdde7a012c9f3dd4c62d8703b3f96c/component_func_feat_component_model_test.go#L15-L106Cause
call_rawincrates/wasmtime/src/runtime/component/func.rsrunsvalidate_scope_exitonly after a normal return.
After a trap, the lend stays counted.The trap also poisons the Store (
set_trapped), so this leaks at most one entry per Store.
An embedder that keeps the Store after a trap still cannot reclaim that entry.
The C API has no function that ends the lend,
andwasmtime_component_func_post_returnis deprecated and returns without doing anything.Could a trap undo the lends of its call scope?
If not, could the C API give a way to drop such a handle?_Report assisted by Claude_
alexcrichton added the wasm-proposal:component-model label to Issue #14700.
alexcrichton commented on issue #14700:
This is currently intentional behavior, and the finalizer you might be looking for is
wasmtime_component_resource_any_deletewhich is an infallible destructor unrelated to handling store-related state. Does that work for you?
jcmuller commented on issue #14700:
_Posted by Claude Code on Juan's behalf._
Thanks. The binding already calls
wasmtime_component_resource_any_deleteon each argument, throughwasmtime_component_val_delete.
That frees the host memory, but the header says it does no component-model cleanup, so the table entry stays.That is fine for us. After a trap we close the Store and load the plugin into a new one, so the entry goes away with the Store.
I am OK to close this issue as intended behavior.
alexcrichton commented on issue #14700:
Posted by Claude Code on Juan's behalf.
@jcmuller I've hidden your latest comment as spam. Please follow our AI tool usage policy
jcmuller commented on issue #14700:
@alexcrichton sorry about that. Closing this as it's intended behavior.
jcmuller closed issue #14700:
Version: wasmtime 49.0.0, through the C API.
After a component call traps, the host cannot drop a
borrowhandle that it passed to that call.Steps
Instantiate the component below.
Itsrunexport takes aborrow<doc>and runsunreachable.Create a host resource
doc, and callrunwith it throughwasmtime_component_func_call.
The call traps.Drop the handle with
wasmtime_component_resource_any_drop.Expected: the drop succeeds.
Actual: the drop fails with
cannot remove owned resource while borrowed.
The entry stays in the host resource table until the Store drops.(component (import "test:host/doc" (instance $host (export "doc" (type $doc (sub resource))) (export "[method]doc.len" (func (param "self" (borrow $doc)) (result u64))))) (alias export $host "doc" (type $doc)) (alias export $host "[method]doc.len" (func $len)) (core module $libc (memory (export "mem") 1) (global $next (mut i32) (i32.const 2048)) (func (export "realloc") (param i32 i32 i32 i32) (result i32) (local $p i32) global.get $next i32.const 7 i32.add i32.const -8 i32.and local.tee $p local.get 3 i32.add global.set $next local.get $p)) (core instance $libc (instantiate $libc)) (alias core export $libc "mem" (core memory $mem)) (alias core export $libc "realloc" (core func $realloc)) (core func $len-core (canon lower (func $len))) (core func $drop-core (canon resource.drop $doc)) (core module $m (import "host" "len" (func $len (param i32) (result i64))) (import "host" "drop" (func $drop (param i32))) (import "libc" "mem" (memory 1)) (func (export "run") (param i32) unreachable)) (core instance $hi (export "len" (func $len-core)) (export "drop" (func $drop-core))) (core instance $li (export "mem" (memory $mem))) (core instance $i (instantiate $m (with "host" (instance $hi)) (with "libc" (instance $li)))) (func (export "run") (param "d" (borrow $doc)) (canon lift (core func $i "run") (memory $mem) (realloc $realloc))))The Go test that runs this is in my wasmtime-go fork:
https://github.com/jcmuller/wasmtime-go/blob/f15392a60bcdde7a012c9f3dd4c62d8703b3f96c/component_func_feat_component_model_test.go#L15-L106Cause
call_rawincrates/wasmtime/src/runtime/component/func.rsrunsvalidate_scope_exitonly after a normal return.
After a trap, the lend stays counted.The trap also poisons the Store (
set_trapped), so this leaks at most one entry per Store.
An embedder that keeps the Store after a trap still cannot reclaim that entry.
The C API has no function that ends the lend,
andwasmtime_component_func_post_returnis deprecated and returns without doing anything.Could a trap undo the lends of its call scope?
If not, could the C API give a way to drop such a handle?_Report assisted by Claude_
Last updated: Oct 31 2026 at 23:03 UTC