ReviewOS

zig-utils/zig-js

GC: release child-realm native graphs cleanly after active moving calls

#831
Closed chris opened this 9 days ago ·
9 days ago

Discovered while isolating #827 from native realm restoration.

Exact local witness: retain only a foreign realm's Array.of native, drop the realm global handle, and invoke it with a constructor whose body triggers the deterministic moving safepoint. The operation completes correctly in both forced modes, but the allocator gate reports six leaked allocations per context from the child realm's WebAssembly.instantiateStreaming native property metadata (cold state, attribute map, and owned keys). Keeping an explicit child-realm prototype/global reference avoids the leak, so this is a realm graph/finalization ownership defect rather than Array result semantics.

The issue is separate from #827: the Array operand/result roots can be correct while the now-unreachable child realm tears down incompletely.

Acceptance:

  • Add the exact retained-native/dropped-global moving witness in both forced modes.
  • Identify and repair the missing trace, relocation, or finalization ownership edge; no pinning, leak suppression, GC disabling, or retained-global workaround.
  • Prove zero allocator leaks and no double-finalization under repeated moving/full collection.
  • Run unsuppressed TSan, full native tests, and relevant realm/GC stress coverage.

Related: #827 and parent #465.

9 days ago

Prerequisite ownership work landed under #830 in 556f25f6: built-in defining-realm private data is now an explicit traced and relocated GC edge. This does not close #831. The exact retained-native/dropped-global repeated full/moving collection witness and zero-finalization-leak proof remain to be implemented and measured independently.

9 days ago

Re-audit on current main after #830: the exact retained Array.of / dropped realm-handle witness now completes in both forced execution modes with zero allocator leaks. The ownership repair is the strong native_realm edge landed in 556f25f6: the retained native marks its defining Environment and moving GC rewrites the private-data pointer.

I have strengthened the closure witness beyond the original reproducer. In each mode it drops the realm wrapper, performs four retained-native calls with four asserted moving nursery collections, runs a full collection and quiescent compaction after every call, then releases the final native edge and repeats terminal full-collection/compaction twice. Focused Debug passes 1/1 with zero failures/leaks. Unsuppressed TSan and full integration are next; #831 remains open until those gates finish.

9 days ago

Closure proof landed directly on main as f4f00803 — test(gc): prove retained native realm teardown (#831).

The production ownership repair is the strong traced/relocated native_realm edge from 556f25f6. The new permanent witness proves the exact retained Array.of remains callable after its child realm wrapper is reclaimed, then proves final release is clean:

  • forced tree-walker and required bytecode
  • 4/4 asserted moving nursery collections per mode
  • at least 4 full collections per mode, with quiescent compaction after each call
  • 2 terminal full-collection/compaction cycles after releasing the native
  • focused Debug 1/1, zero failures/leaks
  • unsuppressed TSan 1/1, zero failures/leaks/races
  • full Debug 2,218 passed / 1 platform skip / 0 failed or leaked across 2,219 tests
  • test-only closure; 0 test262 flips

This closes the previously observed six-allocation child-realm metadata leak without pinning, suppression, disabled GC, or retaining the realm global.

Sign in to comment on this issue.