Skip to content

gc-ratchet: 10_store_receiver_across_alloc runs ZERO minor collections on main — the probe is inert and must not be re-pinned #9832

Description

@proggeramlug

Found while classifying the 43 cells in #9829 for a re-pin. This one must not
be pinned
, and it is a coverage defect rather than a counter movement.

The probe no longer collects

From the measurement artifact of main's own scheduled run (33989581881,
d36a1af0c), against the pinned baseline:

metric baseline today
minor_cycles 1 0
step_cycles 1 0
copied_objects 8,160 0
copied_bytes 506,200 0
freed_bytes 8,930,928 0
heap_used_bytes 220,384 464,072 (+110.57 %)

freed_bytes = 0 rules out "a minor ran and found nothing live" — no
collection ran at all.

Every other probe in the suite still collects (minor_cycles 1–22, unchanged
or ±1), so this is specific to probe 10, not a suite-wide pacing shift.

Why that is worse than a regression

The probe's own header says so:

a[i] = v evaluates the receiver first and the value last — spec order — so
the receiver sits in an SSA register while the RHS runs. When the RHS
allocates, an evacuating minor can relocate the array underneath it […]
THREE THINGS MAKE THIS PROBE BITE, and dropping any one of them makes it
silently measure nothing.

The third is "the RHS allocates", i.e. a minor must actually fire. With
minor_cycles = 0 there is no evacuation, so there is no window, so the probe
cannot catch the stale-root class it exists for (the #6970 / #9523 family:
a root slot rewritten by evacuation while a register still holds the old
address). It is the only probe covering that shape.

heap_used_bytes +110.57 % is a consequence of this, not a retention
regression: nothing was collected, so the post-gc() residue is whatever the
bump allocator left. Reading it as a memory regression would be the same
mistake as reading 12_large_live_set.heap_used_bytes that way (see #9829) —
these are stranded-detectors sampled after an explicit gc(), not memory
figures.

Why nobody saw it

gc-ratchet has been red on main since 2026-08-18 for unrelated reasons
(#9829), and its failures are unwatched (#9830). The suite even has a check
named test_a_probe_that_stopped_collecting_fails — the machinery to catch
this exists and did produce rows. Nobody read them.

So the probe has been inert for some part of nineteen days, and the mechanism
that would have said so was drowned in an unrelated red. That is the concrete
cost of #9830 and the best argument for it.

What this blocks

The #9829 re-pin cannot include this probe's four cells. Pinning
minor_cycles = 0 would permanently bless an inert probe — a green gate that
proves nothing, which is exactly the failure mode this suite's own header
warns about.

The decision I cannot make alone

Two orders, and they give different artefacts:

  1. Fix the probe first, then pin. Restoring a minor changes this probe's
    numbers again, so a pin taken now would be stale for it either way. Cleanest,
    but blocks the pin on a probe repair.
  2. Pin the other 39 now and carry probe 10 as an explicit, documented
    exclusion with this issue as its receipt, then remove the exclusion when the
    probe is repaired. Gets a working gate back tomorrow, at the cost of one
    probe's coverage being formally acknowledged as absent rather than silently
    absent.

I lean to (2) — a gate that works for 13 probes with one honestly-declared gap
beats nineteen more days of nothing — but which probe-side change restores the
minor is a judgement for whoever owns the probe.

Repair direction, not a diagnosis

I have not bisected when the minor stopped. Given the suite's own note that the
probe needs an allocating RHS, the likely cause is that the probe's allocation
volume no longer reaches the nursery threshold — plausibly a consequence of
the object-size reduction in #8313 (56 → 40 bytes, see #9829), which would mean
the probe simply needs to allocate more to keep biting. That is a guess; the
counters above are the facts.

Cross-references: #9829 (the nineteen-day red and its bisection), #9830 (a red
scheduled main run should open an issue), #8313 (the object shrink).

https://claude.ai/code/session_014UZWia6L37DpA93VLtNK9m

Activity

  1. proggeramlug commented on Sep 6, 2026

    @proggeramlug
    ContributorAuthor

    Fixed in #9833 — and the sweep you would ask for next: five more probes are at the same cliff

    The fix is margin, verified by sabotage; details in #9833. The part worth
    promoting out of that PR is what the same measurement says about the rest of the
    suite.

    minor_cycles per probe, pinned baseline vs main @ d36a1af0c (from the
    gate's own artifact, run 33989581881):

    probe baseline today
    01_nursery_churn 1 1 at the cliff
    02_survivor_promotion 1 1 at the cliff
    03_cross_gen_writes 1 1 at the cliff
    04_dead_after_deep_stack 6 7
    05_closure_capture 3 3
    06_string_retention 5 4
    07_array_grow_evacuate 5 5
    08_map_set_sidetables 5 6
    09_try_catch_roots 1 1 at the cliff
    10_store_receiver_across_alloc 1 0 already inert
    11_collect_at_depth 1 1 at the cliff
    12_large_live_set 5 5
    13_large_eden_survivors 3 3
    14_grow_then_churn 21 22

    Six of fourteen probes were pinned at minor_cycles == 1, and one of them has
    already fallen to 0.
    Probe 10 is not a special case; it is the first of a
    cohort. Every one of 01, 02, 03, 09, 11 goes inert on the next
    allocation win of any size — and the campaign is currently landing allocation
    wins deliberately.

    That also explains why the tool's guard did not save us. gc_ratchet.py refuses
    to pin minor_cycles < 1, which catches a probe that is already dead. It
    happily pins minor_cycles == 1, which is the state that produces one. The
    guard is one off from where it needs to be
    , and a plausible follow-up is to
    refuse to pin minor_cycles < 2 and re-size the five above — or, better, to
    have the probes assert their own margin rather than depend on a pinning-time
    check.

    I am not taking that in this pass; #9833 fixes the one probe that is actually
    broken and records the invariant in its header. Raising the guard would re-size
    five probes and re-pin the suite, which belongs with the #9829 pin rather than
    before it.

    https://claude.ai/code/session_014UZWia6L37DpA93VLtNK9m

  2. proggeramlug commented on Sep 6, 2026

    @proggeramlug
    ContributorAuthor

    Fixed by #9833, landed on main via merge train #9875. The train merged as its own branch, so the close-keyword never fired — closing manually, verified on main.

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