Summary
finalize_collected_dead_buffer (crates/perry-runtime/src/buffer/header.rs:483) exists specifically to drop "every registry/side-table entry keyed by a dead buffer's address" — its own doc comment cites preventing "the #6080 ABA class". But it only prunes three tables:
BUFFER_REGISTRY
ARRAY_BUFFER_REGISTRY
BUFFER_AB_ALIAS
Two sibling address-keyed registries are never pruned — anywhere:
DATA_VIEW_REGISTRY
SHARED_ARRAY_BUFFER_REGISTRY
Verified on origin/main: git grep 'DATA_VIEW_REGISTRY\|SHARED_ARRAY_BUFFER_REGISTRY' has zero .remove / .retain / prune sites.
Why it matters
Two consequences, the same pair already proven for the WebCrypto tables:
- Unbounded leak — one permanent entry per
DataView / SharedArrayBuffer ever created, for the life of the process.
- ABA / address reuse —
arena_reset_empty_blocks resets a fully-empty block's offset to 0 while keeping its base pointer, so a reset block re-issues the same addresses. A stale entry keyed on a reused address then answers for a completely unrelated object.
Precedent
Exactly this was just found and fixed for the WebCrypto/KeyObject tables in #6302 / PR #6310: a probe over 60k subtle.importKey HMAC keys with all JS references dropped showed 59,998 of 60,000 metadata entries retained after the buffers were swept. CRYPTO_KEY_META_REGISTRY, SECRET_KEY_REGISTRY, UINT8ARRAY_FROM_CTOR, three process-global external_* maps, and perry-stdlib's CRYPTO_KEY_REGISTRY had all been left off the finalizer. They are now added.
DATA_VIEW_REGISTRY and SHARED_ARRAY_BUFFER_REGISTRY are the remaining members of that class.
Note on pinning
Do not "fix" this by pinning the backing buffers: GC_FLAG_PINNED objects are not traced (gc/cycle.rs only enqueues when flags & (MARKED|PINNED) == 0), so pinning trades a stale entry for an untraced-reference walk and makes the leak permanent by design. Prune in the sweep, as #6310 does.
Suggested fix
Add both tables to finalize_collected_dead_buffer, and add a leak regression test in the shape of the one added by PR #6310 (allocate N, drop refs, force a full collection, assert the registry drains).
Summary
finalize_collected_dead_buffer(crates/perry-runtime/src/buffer/header.rs:483) exists specifically to drop "every registry/side-table entry keyed by a dead buffer's address" — its own doc comment cites preventing "the #6080 ABA class". But it only prunes three tables:BUFFER_REGISTRYARRAY_BUFFER_REGISTRYBUFFER_AB_ALIASTwo sibling address-keyed registries are never pruned — anywhere:
DATA_VIEW_REGISTRYSHARED_ARRAY_BUFFER_REGISTRYVerified on
origin/main:git grep 'DATA_VIEW_REGISTRY\|SHARED_ARRAY_BUFFER_REGISTRY'has zero.remove/.retain/ prune sites.Why it matters
Two consequences, the same pair already proven for the WebCrypto tables:
DataView/SharedArrayBufferever created, for the life of the process.arena_reset_empty_blocksresets a fully-empty block's offset to 0 while keeping its base pointer, so a reset block re-issues the same addresses. A stale entry keyed on a reused address then answers for a completely unrelated object.Precedent
Exactly this was just found and fixed for the WebCrypto/KeyObject tables in #6302 / PR #6310: a probe over 60k
subtle.importKeyHMAC keys with all JS references dropped showed 59,998 of 60,000 metadata entries retained after the buffers were swept.CRYPTO_KEY_META_REGISTRY,SECRET_KEY_REGISTRY,UINT8ARRAY_FROM_CTOR, three process-globalexternal_*maps, and perry-stdlib'sCRYPTO_KEY_REGISTRYhad all been left off the finalizer. They are now added.DATA_VIEW_REGISTRYandSHARED_ARRAY_BUFFER_REGISTRYare the remaining members of that class.Note on pinning
Do not "fix" this by pinning the backing buffers:
GC_FLAG_PINNEDobjects are not traced (gc/cycle.rsonly enqueues whenflags & (MARKED|PINNED) == 0), so pinning trades a stale entry for an untraced-reference walk and makes the leak permanent by design. Prune in the sweep, as #6310 does.Suggested fix
Add both tables to
finalize_collected_dead_buffer, and add a leak regression test in the shape of the one added by PR #6310 (allocate N, drop refs, force a full collection, assert the registry drains).