You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
gc: CONSOLE_INSTANCES is a rekeyed address-keyed table with no dead-key prune #8190
CONSOLE_INSTANCES (crates/perry-runtime/src/builtins/console.rs) is keyed by the raw heap address of the console instance object (js_object_alloc(CONSOLE_INSTANCE_CLASS_ID, 0) as usize, console.rs:1037-1049) and its key is rekeyed by scan_console_log_singleton_roots_mut (console.rs:210, visit_metadata_usize_slot) — rewritten if the object moved, deliberately not marked.
Nothing tells the table when that object dies. It is absent from gc::dead_owner's DEAD_KEY_PRUNES fan-out and from gc_type_clear_dead_payload_side_tables; the only other uses are lookups at console.rs:960/1039.
That is the #8040 shape. A dead key is not merely a leak for a rekeyed table: the arena recycles the address, the recycled bytes are read as a GcHeader, and a coincidental GC_FLAG_FORWARDED byte makes the rewrite pass follow a garbage forwarding pointer. #8174 narrowed what can be followed out of such a header; it did not remove the dead key.
Fix: add a prune_dead_console_instance_owners and wire it into DEAD_KEY_PRUNES in crates/perry-runtime/src/gc/dead_owner.rs, with a dead_owner_side_tables.rs case.
Declared as open_gap in scripts/gc_rekeyed_key_tables.json; closing this must delete that entry, or the gate fails on the stale exemption.
The #8174 registry gate enumerated every visit_metadata_* rekey site and required a written verdict for each; this table had none, and rather than declare it an exemption the prune is wired in. Its entry in scripts/gc_rekeyed_key_tables.json now names a registered DEAD_KEY_PRUNES member, and MAX_OPEN_GAPS is 0 — the manifest ships with no declared gaps at all.
Covered by a pair of cases in gc/tests/dead_owner_side_tables.rs: the prune fires (dead owner, one collection, the table observably shrinks) and its inverse (a rooted owner's entry survives).
Independent evidence that these were real stale keys and not a theoretical hazard: with only #8174's forwarding-target discriminators in place and none of these prunes, the production Next App Route forced-evacuation workload logged 988[gc-forwarding] refused_sources events — stale forwarding headers reaching a rewrite walk. With the six prunes in, the same workload logs 0 across 458 copying minors and 182,977 copied objects.
Activity
fix(gc): refuse a forwarding walk out of, or into, a non-object (#8174)
fix(gc): give the six unpruned rekeyed tables a death story (#8190-#8195
proggeramlug commentedon Aug 16, 2026
Fixed in #8196.
The #8174 registry gate enumerated every
visit_metadata_*rekey site and required a written verdict for each; this table had none, and rather than declare it an exemption the prune is wired in. Its entry inscripts/gc_rekeyed_key_tables.jsonnow names a registeredDEAD_KEY_PRUNESmember, andMAX_OPEN_GAPSis 0 — the manifest ships with no declared gaps at all.Covered by a pair of cases in
gc/tests/dead_owner_side_tables.rs: the prune fires (dead owner, one collection, the table observably shrinks) and its inverse (a rooted owner's entry survives).Independent evidence that these were real stale keys and not a theoretical hazard: with only #8174's forwarding-target discriminators in place and none of these prunes, the production Next App Route forced-evacuation workload logged 988
[gc-forwarding] refused_sourcesevents — stale forwarding headers reaching a rewrite walk. With the six prunes in, the same workload logs 0 across 458 copying minors and 182,977 copied objects.fix(gc): refuse a forwarding walk out of, or into, a non-object (#8174)
fix(gc): give the six unpruned rekeyed tables a death story (#8190-#8195
fix(gc): refuse a forwarding walk out of, or into, a non-object, and …