Skip to content

Make limiter growth callbacks unwind-safe and kind-aware - #8

Open
kmatasfp wants to merge 1 commit into
golem-wasmtime-v46.0.1-p3from
kaurmatas/gol-424-425-limiter-growth-callbacks
Open

Make limiter growth callbacks unwind-safe and kind-aware#8
kmatasfp wants to merge 1 commit into
golem-wasmtime-v46.0.1-p3from
kaurmatas/gol-424-425-limiter-growth-callbacks

Conversation

@kmatasfp

@kmatasfp kmatasfp commented Aug 10, 2026

Copy link
Copy Markdown

Implements GOL-424 and GOL-425.

Summary

  • emit the committed-growth notification only after the owning VMContext holds the new base pointer and length, so a panicking limiter cannot leave a store inconsistent
  • for the GC heap, emit it only after the grown memory and its size delta have been returned to the collector
  • add a public MemoryKind to memory_growing, memory_grown, and memory_grow_failed so embedders can account for guest linear memory separately from Wasmtime's internal GC heap
  • emit memory_grown for MemoryKind::GcHeap as well, so every permitted growth is resolved exactly once
  • add regression tests for panicking notifications, GC heap growth that succeeds, is rejected, and fails, and ordinary linear-memory growth

Behavior

LocalMemory::grow no longer notifies the limiter. It returns the old and new sizes, and each caller emits the notification once the state an unwind could otherwise strand is in place. Instance::memory_grow does so after publishing the refreshed VMMemoryDefinition; StoreOpaque::grow_gc_heap does so after TakenGcHeap has put the grown memory and delta_bytes_grown back into the GcStore. The deferred reference-counting collector feeds that delta straight to FreeList::add_capacity, so notifying earlier could strand the new capacity and force an immediate second growth.

Because memory_grown runs arbitrary embedder code, this matters whenever that code can panic and the panic is caught — Golem's callback is intended to be infallible but takes mutexes with .unwrap(). Previously a caught panic could leave compiled Wasm observing stale bounds, or a VMContext pointing at an allocation that growth had already relocated and freed. After the change the memory reports its new size, the host and guest views of it agree, and the instance and store remain usable.

The three growth callbacks now carry the kind of heap being grown. Previously memory_growing fired for every MemoryKind while memory_grown fired only for LinearMemory, so a successful GC heap growth left an embedder holding a reservation made in memory_growing that it could never release; repeated GC growth eroded admission headroom until the store unloaded. Every permitted growth is now resolved by exactly one memory_grown or memory_grow_failed carrying the kind it was requested under, and GC heap capacity is never presented as guest linear memory.

Initial allocations still call memory_growing without a resolving callback, and a request the memory's type cannot represent is refused before the limiter is consulted, so memory_grow_failed can arrive unpaired. Both are pre-existing and now stated explicitly in the trait docs; with the kind available an embedder can decline to reserve for GcHeap at all. A store only allocates a GC heap when the gc feature is compiled in, Config::wasm_gc is enabled, and something actually allocates a GC object — embedders that leave GC off will never observe MemoryKind::GcHeap.

Success, rejection, and memory_grow_failed behavior is otherwise unchanged for non-GC memories, and shared memories continue to receive no callbacks.

Testing

Regression tests live in runtime::limits::tests rather than tests/all/limits.rs because tests/all does not currently compile on this branch, for reasons unrelated to this change (host_funcs.rs and traps.rs reference a removed wasmtime_wasi::p1 API). tests/all/limits.rs is still updated for the new signatures and passes locally with that breakage patched out. Each new test was confirmed to fail against the previous behavior.

Golem

  • Golem needs updating to this revision: its ResourceLimiter implementation must take the new MemoryKind parameter, and should skip MemoryKind::GcHeap when reserving and releasing linear-memory admission grants
  • whether Golem's current configuration can initialize or grow a Wasmtime GC heap still needs confirming on the Golem side

GOL-424: `memory_grown` ran inside `LocalMemory::grow`, after the backing
allocation had committed but before the owning `VMContext` was refreshed.
Since the callback runs arbitrary embedder code, a panic caught there left
compiled wasm observing stale bounds, or a `VMContext` pointing at a freed
allocation when growth relocated the base.

`LocalMemory::grow` no longer notifies. `Memory::grow` returns the old and
new sizes and callers emit the notification once the state that unwinding
could otherwise strand is in place: `Instance::memory_grow` after publishing
the refreshed `VMMemoryDefinition`, and `StoreOpaque::grow_gc_heap` after
`TakenGcHeap` has returned the grown memory and its size delta to the
collector -- the DRC collector feeds that delta straight to
`FreeList::add_capacity`, so notifying earlier could strand the new capacity.

GOL-425: `memory_growing` fired for every `MemoryKind` but `memory_grown`
only for `LinearMemory`, so a successful GC heap growth left an embedder
holding a reservation it made in `memory_growing` and could never release.
`memory_growing`, `memory_grown` and `memory_grow_failed` now all carry a
public `MemoryKind`, so every permitted growth is resolved exactly once with
the kind it was requested under, and GC heap capacity is never mistaken for
guest linear memory.

Regression tests in `runtime::limits::tests` cover the panicking callback for
fixed-base and relocating linear memory and for the DRC GC heap, GC heap
growth that succeeds, is rejected, and fails in the allocator, and ordinary
linear-memory growth. Each was confirmed to fail against the previous
behavior. They live here rather than in `tests/all/limits.rs` because
`tests/all` does not currently compile on this branch, for unrelated reasons;
that file is still updated for the new signatures and passes locally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant