05_closure_capture retains +16.44% with every collector counter unchanged
Surfaced the moment the GC ratchet started measuring again (#7557 — its probes had been skipped entirely since 2026-08-05 because a harness-validation step ran before them).
Of the 10 gating breaches that became visible, most are improvements wearing a two-sided band. This one is not. Reported by the agent that restored the ratchet, on two machine classes (the pinned quiet mini and a MacBook), with retention and counters reproducing bit-for-bit across hosts:
| probe |
retention |
collector counters |
05_closure_capture |
+16.44% |
+0.00% on every one |
02_survivor_promotion |
+2.77% |
— |
Same minor_cycles, copied_objects, copied_bytes, promoted_objects, promoted_bytes, freed_bytes — identical work, more retained. That combination is the interesting one: it rules out "the collector ran differently" and points at something being kept alive that previously was not. A closure-capture probe holding 16% more is exactly the shape of an over-broad root or a capture cell that stopped being released.
Why it was invisible
The ratchet did not run between 2026-08-05 and #7557. In that window roughly fifteen GC-affecting changes landed: #7532 (typed-shape layout at allocation), #7535 (shape-install memo), #7536 (write-barrier gating), #7546 (spill-path value rooting), #7553 (tape out of the old generation), #7550/#7552 (for-init type propagation), plus the rooting stack #7495/#7516/#7527/#7529.
Several of those add rooting. #7516 alone rooted 14 sites, #7527 more, #7529 three more — and a root that is broader or longer-lived than necessary retains. That is the first place I would look, and it is worth saying plainly: these fixes were correct and must not be reverted (they closed real use-after-frees), but "more correct rooting" and "more retention" are the same lever pulled in opposite directions.
Suggested method
Bisect the probe's retention across that window with the ratchet itself now that it works — gc_ratchet.py measure --probe 05_closure_capture at each candidate commit on the pinned host. The counters being flat means a PERRY_GC_DIAG trace diff between the endpoints should show the difference as a retained-object delta rather than a cycle-behaviour one.
Note the measurement caveat from #7558: every probe reads process.memoryUsage() after an explicit gc(), which forces a conservative stack scan that systematically over-retains (measured at 8.28 MB / 16% on 12_large_live_set). Run the comparison with PERRY_CONSERVATIVE_STACK_SCAN=off as well, so a false-root artifact is not mistaken for a real regression — or confirmed as one.
Acceptance
Either the +16.44% is attributed to a specific commit and fixed, or it is shown to be a measurement artifact of the conservative scan and the probe's methodology is corrected. Do not re-pin it away without an answer — that is the decision the ratchet exists to force.
05_closure_captureretains +16.44% with every collector counter unchangedSurfaced the moment the GC ratchet started measuring again (#7557 — its probes had been skipped entirely since 2026-08-05 because a harness-validation step ran before them).
Of the 10 gating breaches that became visible, most are improvements wearing a two-sided band. This one is not. Reported by the agent that restored the ratchet, on two machine classes (the pinned quiet mini and a MacBook), with retention and counters reproducing bit-for-bit across hosts:
05_closure_capture02_survivor_promotionSame
minor_cycles,copied_objects,copied_bytes,promoted_objects,promoted_bytes,freed_bytes— identical work, more retained. That combination is the interesting one: it rules out "the collector ran differently" and points at something being kept alive that previously was not. A closure-capture probe holding 16% more is exactly the shape of an over-broad root or a capture cell that stopped being released.Why it was invisible
The ratchet did not run between 2026-08-05 and #7557. In that window roughly fifteen GC-affecting changes landed: #7532 (typed-shape layout at allocation), #7535 (shape-install memo), #7536 (write-barrier gating), #7546 (spill-path value rooting), #7553 (tape out of the old generation), #7550/#7552 (for-init type propagation), plus the rooting stack #7495/#7516/#7527/#7529.
Several of those add rooting. #7516 alone rooted 14 sites, #7527 more, #7529 three more — and a root that is broader or longer-lived than necessary retains. That is the first place I would look, and it is worth saying plainly: these fixes were correct and must not be reverted (they closed real use-after-frees), but "more correct rooting" and "more retention" are the same lever pulled in opposite directions.
Suggested method
Bisect the probe's retention across that window with the ratchet itself now that it works —
gc_ratchet.py measure --probe 05_closure_captureat each candidate commit on the pinned host. The counters being flat means aPERRY_GC_DIAGtrace diff between the endpoints should show the difference as a retained-object delta rather than a cycle-behaviour one.Note the measurement caveat from #7558: every probe reads
process.memoryUsage()after an explicitgc(), which forces a conservative stack scan that systematically over-retains (measured at 8.28 MB / 16% on12_large_live_set). Run the comparison withPERRY_CONSERVATIVE_STACK_SCAN=offas well, so a false-root artifact is not mistaken for a real regression — or confirmed as one.Acceptance
Either the +16.44% is attributed to a specific commit and fixed, or it is shown to be a measurement artifact of the conservative scan and the probe's methodology is corrected. Do not re-pin it away without an answer — that is the decision the ratchet exists to force.