Stream: git-wasmtime

Topic: wasmtime / issue #14700 component: no way to end a host b...


view this post on Zulip Wasmtime GitHub notifications bot (Oct 13 2026 at 14:35):

jcmuller opened issue #14700:

Version: wasmtime 49.0.0, through the C API.

After a component call traps, the host cannot drop a borrow handle that it passed to that call.

Steps

  1. Instantiate the component below.
    Its run export takes a borrow<doc> and runs unreachable.

  2. Create a host resource doc, and call run with it through wasmtime_component_func_call.
    The call traps.

  3. 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-L106

Cause

call_raw in crates/wasmtime/src/runtime/component/func.rs runs validate_scope_exit only 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,
and wasmtime_component_func_post_return is 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_

view this post on Zulip Wasmtime GitHub notifications bot (Oct 13 2026 at 16:44):

alexcrichton added the wasm-proposal:component-model label to Issue #14700.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 13 2026 at 16:45):

alexcrichton commented on issue #14700:

This is currently intentional behavior, and the finalizer you might be looking for is wasmtime_component_resource_any_delete which is an infallible destructor unrelated to handling store-related state. Does that work for you?

view this post on Zulip Wasmtime GitHub notifications bot (Oct 13 2026 at 17:31):

jcmuller commented on issue #14700:

_Posted by Claude Code on Juan's behalf._

Thanks. The binding already calls wasmtime_component_resource_any_delete on each argument, through wasmtime_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.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 13 2026 at 17:33):

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

view this post on Zulip Wasmtime GitHub notifications bot (Oct 13 2026 at 17:35):

jcmuller commented on issue #14700:

@alexcrichton sorry about that. Closing this as it's intended behavior.

view this post on Zulip Wasmtime GitHub notifications bot (Oct 13 2026 at 17:35):

jcmuller closed issue #14700:

Version: wasmtime 49.0.0, through the C API.

After a component call traps, the host cannot drop a borrow handle that it passed to that call.

Steps

  1. Instantiate the component below.
    Its run export takes a borrow<doc> and runs unreachable.

  2. Create a host resource doc, and call run with it through wasmtime_component_func_call.
    The call traps.

  3. 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-L106

Cause

call_raw in crates/wasmtime/src/runtime/component/func.rs runs validate_scope_exit only 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,
and wasmtime_component_func_post_return is 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