Skip to content

gc: pin latch aborts relocating a PINNED young Map on a preflight-skipped cycle (zod dep-corpus, exit 134) — and gc_pin_sites.py says OK #7990

Description

@proggeramlug

Found while re-running #7803's workload on bdfcba4a2 (v0.5.1499), now that the
zod dependency corpus links again (#7980).

What happens

test-files/gc-dep-corpus/main.ts under a rate-1 seeded schedule aborts with
exit 134 and the runtime's own latch report:

[gc-pin-latch] FATAL: copying minor is about to relocate a PINNED young object
  on a preflight-skipped cycle. header=0x2db2f681350 obj_type=8 size=731 flags=0x37
The young-pin latch (gc/pin.rs) is incomplete: some site sets GC_FLAG_PINNED
without going through gc::pin_object. Find it with `python3 scripts/gc_pin_sites.py`
and route it through pin_object (#7645).

[gc-schedule] FAILURE (signal 6) under seed=15
[gc-schedule]   safepoints=1870 scheduled_collections=1870

This is crates/perry-runtime/src/gc/copying.rs:691 — the latch #7645 added on
purpose — firing. So this is a detected fault, not a silent one, which is
the latch working as designed.

Reproduce

cargo build --release -p perry -p perry-runtime-static -p perry-stdlib-static
npm ci --ignore-scripts --no-audit --no-fund
PERRY_RUNTIME_DIR=target/release PERRY_NO_AUTO_OPTIMIZE=1 PERRY_DISABLE_BUILD_CACHE=1 \
  target/release/perry test-files/gc-dep-corpus/main.ts -o /tmp/w

RATE=1 TIMEOUT=1800 PERRY_GC_PROTECT_FROMSPACE=0 PERRY_GC_DIAG=1 \
  ./scripts/gc_schedule_fuzz.sh /tmp/w 16

It is intermittent. Seed 15 aborted once in a 16-seed sweep and then passed
3/3 on re-run with the identical command, so do not treat a passing seed 15 as a
refutation — the seeded schedule does not replay exactly on this workload
(safepoints drifts ~4% run to run at a fixed seed). Budget a sweep, not a run.

What the header says

  • obj_type=8 = GC_TYPE_MAP. The corpus keeps its registry in Maps
    (SCHEMAS / CALLBACKS in test-files/gc-dep-corpus/shared.ts), walked by
    parseRegistered().
  • size=731.
  • flags=0x37 = MARKED | ARENA | PINNED | INTERNED | TENURED. Worth a second
    look on its own: TENURED is set on an object the copying minor is treating
    as young.

The FATAL's own remediation does not apply

The message says to find a site that sets GC_FLAG_PINNED without going through
gc::pin_object. On this tree that tool disagrees:

$ python3 scripts/gc_pin_sites.py
gc_pin_sites: OK — every pin originates in gc::pin_object
              (2 allowlisted exception(s), 56 GC_FLAG_PINNED tokens scanned).

So either one of the two allowlisted exceptions is responsible, or the pin is
legitimate and the defect is in the preflight-skip decision
(Self::preflight_walks_decided, copying.rs:1111) rather than in pin
bookkeeping. Either way the printed remediation currently sends the reader at a
hypothesis its own tool refutes, and should be reworded once the cause is known.

#7645 is closed, hence a new issue rather than a reopen.

Scope note

This is not the #7154-class rooting failure that the same sweep also
produces on other seeds (TypeError: value is not a function,
Cannot read properties of undefined) — those surface cycles later with nothing
for the collector to find, whereas this one is caught at the instant of the
move. Triage of the whole sweep is in gc-handoff/ZOD-NOTES.md (#7989).

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