Skip to content

GC follow-ups from #7733: one-way prototype latch (#7510 shape), missing positive-direction pacing test, unmeasured grow-then-churn transition, advisory-only ratchet #7737

Description

@proggeramlug

Follow-ups from the audit of #7733 (merged as v0.5.1427). None blocked the merge; all four are cheap and concrete.

1. OBJECT_PROTOTYPES_NONEMPTY is a one-way latch — the #7510 shape, with a third victim

object_static_prototype_owner_moved's new fast path is gated on OBJECT_PROTOTYPES_NONEMPTY.load(Acquire) (object/prototype_chain.rs:~340). Nothing ever resets that latch to falseincluding prune_dead_object_prototype_owners when its retain() empties the map back out (prototype_chain.rs:328-335).

So a single Object.setPrototypeOf against a non-meta-capable owner anywhere in a process's lifetime — even one that later dies and gets pruned — permanently disables the fast path for the rest of that process. A long-running server that does one incidental re-prototyping early (of a RegExp, say) silently forfeits the 2.5 M-lock / 2.5 M-hash win, with no signal.

This is pre-existing and shared by all three readers (object_static_prototype:255, prune_dead_object_prototype_owners:329, and now the move hook), not introduced by #7733. But it is exactly #7510's finding — "one immortal side-table entry nullified every is_empty() fast path" — recurring. Fix: reset the latch when prune leaves the map empty, or replace it with a real emptiness check.

2. No positive-direction test for the recording

declining_to_escalate_records_no_pre_full_reading proves only the negative: a declined escalation leaves pre_in_use == 0. Nothing asserts that a true verdict from the real arena_growth_full_escalation_due() — not the test_note_full_cycle_reclaimed bypass — leaves a non-zero reading behind.

That positive direction is precisely the shape of the first-cut bug #7733's own changelog describes: recording wired at the wrong call sites, update_major_pacing_backoff silently no-op'ing, every test green. The gap exists because forcing due=true deterministically needs control over arena_in_use_bytes(), which isn't mockable today — real, but addressable.

3. The grow-then-churn transition is named but unmeasured

Array-growth forwarding stubs are reclaimable only by a full mark-sweep (arena_growth_full_escalation_due's own doc), and old_reclaim_pressure_due tracks old-gen occupancy, not stub bytes — so it does not substitute for that path.

The scenario: a warm/cache-build phase with a growing all-live structure drives the shift to cap, then the workload transitions to steady array-churn that needs a full to reclaim stubs but never regrows arena in-use past the old 2× threshold. That reclaim is delayed until 8×, i.e. up to 4× more transient RSS for that shape, self-healing after one full once it fires.

Bounded, not unbounded — but neither retain.ts (pure growth) nor tree.ts (always high-yield) exercises it. Both are extremes; the transition between them is the untested middle. A gc_ratchet probe that grows without garbage long enough to hit the cap, then switches to churn, asserting a peak-RSS bound, would close it.

4. gc-ratchet and gc-stress are not required contexts

Confirmed against branch protection — required contexts are lint, cargo-test, parity, compile-smoke, api-docs-drift, security-audit, conformance-smoke-complete. Both gc-ratchet and gc-stress run and can report red without blocking a merge.

That matters for #7733 specifically: 12_large_live_set's heap_used_bytes/heap_total_bytes cells (direction: "increase") are the gate that would catch a footprint regression from the backoff, and #7733's central quantitative claim rests on them. Advisory-only coverage of the exact risk the change carries.

This is CLAUDE.md's own "four ways a gate can be unable to fail", #2 — and its gc-stress example is the precedent. Promotion is a maintainer action; per the same doc a new gate should be observed green on main once first, which recent runs now satisfy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions