Skip to content

gc: SYMBOL_ACCESSOR_PROPERTIES owner keys are rekeyed but never pruned on owner death #8195

Closed

Description

@proggeramlug

SYMBOL_ACCESSOR_PROPERTIES (crates/perry-runtime/src/symbol/accessors.rs) has a two-part key. The sym_key half IS a strong root (visit_usize_slot, accessors.rs:149). The owner half is rekeyed as metadata only — scan_symbol_accessor_roots_mut (accessors.rs:148) and its budgeted twin scan_symbol_accessor_root_slot (accessors.rs:196), both visit_metadata_usize_slot — so it is rewritten if the owner moved and deliberately not marked.

Nothing prunes it on owner death. prune_dead_symbol_property_owners (symbol.rs:528-550), which gc::dead_owner's fan-out does call, touches only SYMBOL_PROPERTIES and SYMBOL_PROPERTY_ATTRS. The accessor table's only bulk removal, clear_all_symbol_accessor_properties_for_object (accessors.rs:31), is reached solely from the handle-recycle path clear_all_symbol_properties_for_object(handle as usize) (symbol/properties.rs:163) — i.e. never for a plain heap object.

Two consequences: the accessor closures held in those entries leak, and — the #8040 shape — the dead owner address survives to the next cycle's rewrite_raw_addr, where recycled bytes can carry a coincidental GC_FLAG_FORWARDED. #8174 narrowed what can be followed out of such a header; it did not remove the dead key.

Fix: extend prune_dead_symbol_property_owners to the accessor table (it already receives the right predicate and is already registered in DEAD_KEY_PRUNES), with a dead_owner_side_tables.rs case covering both the dead-owner drop and the live-owner inverse.

Declared as open_gap in scripts/gc_rekeyed_key_tables.json (two sites); closing this must delete both entries, or the gate fails on the stale exemptions.

Refs #8174, #8168, #8040.

Activity

  1. proggeramlug commented on Aug 16, 2026

    @proggeramlug
    ContributorAuthor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions