Skip to content

fix(gc): the pacing snapshot reports the boundary the predicate uses; the ZealGuard release becomes an assertion (#7729, #7733, #7735 review follow-ups) - #7739

Merged
proggeramlug merged 3 commits into
mainfrom
fix/coderabbit-gc-followups
Aug 10, 2026
Merged

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review follow-ups from #7729 / #7733 / #7735 that were raised and landed anyway. All three PRs stay as merged; nothing here reverts or redoes them.

1. major_pacing_snapshot under-reported the escalation boundary

crates/perry-runtime/src/gc/policy.rs. The snapshot recomputed the boundary as baseline × growth and dropped the floor on the floor of the function:

let (_floor, growth_num) = major_pacing_config();

...while the predicate it mirrors, arena_growth_full_escalation_due, also rejects every reading below that floor. Wherever the floor dominates, the reported threshold named a boundary the collector does not use:

state predicate escalates at old snapshot reported
no full yet (baseline == 0) 32 MB (the floor) 0 — "escalates at any size"
baseline = 4 MB, growth 2 32 MB (the floor) 8 MB
baseline = 64 MB, growth 2 128 MB + 1 128 MB

This matters more than its "minor" label because of why the snapshot exists: #7733 added it so the pacing subject could be asserted live in the GC trace, rather than a gate merely proving nothing threw. A diagnostic that misreports the quantity it exists to prove is this repo's most expensive recurring failure mode (PERRY_GC_FORCE_EVACUATE inert for every gc()-driven test, the matrix's --pressure knob disabling the path it measured, moved= summing two collectors).

The fix is structural rather than a second correct formula: there is now one definition of the boundary, major_pacing_escalation_threshold_bytes. The predicate is literally in_use >= it, and the snapshot reports it verbatim, floor included. None means "no arena reading escalates" — either pacing is disabled (PERRY_GC_MAJOR_PACING_FLOOR_MB=0) or the growth term overflowed usize, which is the same statement about the world, and is why the helper uses checked_* rather than saturating_* (saturating would report usize::MAX and then claim an arena of usize::MAX escalates, which the > clause never would).

The trace key follows the semantics: escalate_at_or_above_bytes, not escalate_above_bytes — the predicate's floor clause is a >=, and the old name was half of why the figure and the decision could disagree. No consumer of the old key exists in scripts/, .github/ or docs/.

Tests. the_reported_escalation_boundary_is_the_one_the_predicate_decides_on checks the named floor-dominates and growth-dominates cases plus baseline-zero, shift, and pacing-disabled, then goes exhaustive over floor × growth × baseline × shift, probing each boundary's own ±1 neighbourhood against an oracle that is a deliberate independent transcription of the four clauses the predicate used to spell out inline (not a call into the code under test). the_shipped_predicate_and_the_shipped_snapshot_read_one_boundary then drives the real predicate against the real snapshot on the live arena, so a future re-split fails even if the pure helper stays correct.

Both are sabotage-checked, and the two sabotages are different on purpose:

  • Restore the pre-fix formula in major_pacing_snapshot only (the actual historical bug) → the_shipped_predicate_and_the_shipped_snapshot_read_one_boundary fails on baseline = 0: escalate_at_or_above_bytes (Some(0)) against a verdict of false on a 0-byte arena. The matrix test passes, correctly — the pure helper was untouched.
  • Drop the floor from the shared helper → the matrix test fails on the named row: left: Some(8388609), right: Some(33554432).

Restored after each; the final tree is byte-identical to the pushed commits.

2. The ZealGuard release assertion was missing

crates/perry-runtime/src/gc/tests/triggers.rs. zeal_holds_the_poll_word_armed_with_nothing_pending asserted PERRY_GC_POLL_ARMED > 0 inside the guard scope and then only narrated the release in a comment. If ZealGuard's Drop ever stopped giving the arm back, the process-global word would stay non-zero for the life of the test binary, every later test would silently take the poll's slow path, and this test would still pass.

It now captures the baseline before the guard and asserts base + 1 inside, base after the drop — the same shape a_deferral_arms_the_poll_word_and_draining_disarms_it already uses a few lines up.

Sabotage-checked: with disarm_poll() commented out of ZealGuard::drop, the test fails (left: 2, right: 1) on the new post-drop assertion; restored, it passes.

3. Wording

Validation

  • cargo test --release -p perry-runtime1977 passed; 0 failed, cargo's own exit code 0 (captured directly, not through a pipe). All three tests confirmed to have run, by name, not merely to have not failed.

  • cargo fmt --all -- --check clean; scripts/check_file_size.sh OK.

  • The trace, end to end. gc-handoff/bench/retain.ts compiled with PERRY_NO_AUTO_OPTIMIZE=1 against this branch's libperry_runtime.a (PERRY_RUNTIME_DIR pinned at the freshly built archives), run under PERRY_GC_TRACE=1:

    minor  baseline=0          shift=0  escalate_at_or_above=33554432    (= the 32 MB floor)
    minor  baseline=0          shift=0  escalate_at_or_above=33554432
    full   baseline=66025968   shift=1  escalate_at_or_above=264103873   (= 66025968 x 4 + 1)
    minor  baseline=66025968   shift=1  escalate_at_or_above=264103873
    

    The first two rows are the fix: baseline = 0 is the pre-first-full state, where the old snapshot printed escalate_above_bytes: 0. Then the escalated full rebaselines to 66 MB, reclaims little, and backs off to shift 1 — so the reported boundary becomes 4x the baseline, above the floor, and growth takes over. The block is present, non-vacuous, and each row is arithmetically consistent with the predicate.

  • Canary: gc-handoff/apps/iso_miss.ts prints checksum 437840 misses 0.

  • Not run locally: scripts/gc_instrument_smoke.sh. Its arm 6 carries a wall-clock budget and this host is heavily loaded, so a local red would not have been informative; CI's runner is the right place for it.

No version bump — maintainer bumps at merge.

Summary by CodeRabbit

  • Bug Fixes

    • Corrected major garbage-collection pacing thresholds and boundary handling.
    • Improved diagnostics for disabled pacing, overflow conditions, and pre-first-collection behavior.
    • Clarified inclusive escalation semantics in GC telemetry.
  • Documentation

    • Updated GC zeal and allocation-pacing guidance, including default poll behavior and failure diagnostics.
    • Refined benchmark descriptions and release documentation.
  • Tests

    • Added coverage for pacing boundaries, overflow cases, telemetry consistency, and poll-arm restoration.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Major-GC pacing now uses a shared inclusive escalation-threshold calculation. Telemetry and tests cover disabled, overflow, and boundary cases. Zeal tests and documentation describe poll-arm restoration and collection-count behavior.

Changes

GC pacing and zeal behavior

Layer / File(s) Summary
Centralized pacing threshold computation
crates/perry-runtime/src/gc/policy.rs, changelog.d/7739-gc-pacing-snapshot-boundary.md
A shared helper calculates inclusive thresholds for floor, growth, backoff, pre-first-full, disabled, and overflow cases. The changelog records the threshold semantics.
Telemetry and threshold validation
crates/perry-runtime/src/gc/telemetry.rs, crates/perry-runtime/src/gc/tests/triggers.rs, changelog.d/7739-gc-pacing-snapshot-boundary.md
Telemetry uses escalate_at_or_above_bytes. Tests validate threshold boundaries and production predicate consistency.
Zeal behavior and documentation updates
crates/perry-runtime/src/gc/tests/triggers.rs, crates/perry-runtime/src/gc/zeal.rs, docs/src/internals/memory-model.md, changelog.d/7729-gc-zeal-allocation-pacing.md, changelog.d/7739-gc-pacing-snapshot-boundary.md, CLAUDE.md, Cargo.toml
Tests verify exact poll-arm restoration. Documentation describes zero-stride pacing, poll gaps, vacuous runs, additional safepoint collections, and version 0.5.1433.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • PerryTS/perry#7733: Both changes update major-GC pacing threshold calculation and diagnostics.
  • PerryTS/perry#7729: This change extends and documents related GC zeal allocation-pacing behavior.
  • PerryTS/perry#7735: Both changes validate GC poll-arm and ZealGuard behavior.

Suggested reviewers: thehypnoo, jdalton

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the two primary fixes: the GC pacing boundary and the ZealGuard release assertion.
Description check ✅ Passed The description thoroughly covers the changes, tests, trace validation, and skipped smoke test, but omits explicit template headings and contains a version-bump inconsistency.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/coderabbit-gc-followups

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/perry-runtime/src/gc/policy.rs (1)

2747-2747: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider guarding the shift against out-of-range values.

1usize << shift panics on debug builds if shift >= usize::BITS. Production keeps the backoff shift capped at 2, so this is not reachable today. major_pacing_escalation_threshold_for is pub(super) and accepts any u32, so a future caller or test could pass a larger shift. checked_shl returns None, which matches the documented "no arena reading can reach this boundary" contract.

♻️ Optional hardening
-    let growth = growth_num.saturating_mul(1usize << shift);
+    let growth = growth_num.saturating_mul(1usize.checked_shl(shift)?);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/gc/policy.rs` at line 2747, Guard the shift in the
growth calculation within major_pacing_escalation_threshold_for by using
checked_shl or equivalent handling for shift values at least usize::BITS.
Preserve the documented no-arena-reading contract by returning the existing
no-boundary result when the shift cannot be represented, while retaining current
behavior for valid shifts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@changelog.d/7729-gc-zeal-allocation-pacing.md`:
- Line 23: Correct the parenthesis placement in
changelog.d/7729-gc-zeal-allocation-pacing.md lines 23-23 and
crates/perry-runtime/src/gc/zeal.rs lines 215-216: change “at (the outermost
microtask-pump boundary, which ...” to “at the outermost microtask-pump boundary
(which ...” in both documentation copies.

---

Nitpick comments:
In `@crates/perry-runtime/src/gc/policy.rs`:
- Line 2747: Guard the shift in the growth calculation within
major_pacing_escalation_threshold_for by using checked_shl or equivalent
handling for shift values at least usize::BITS. Preserve the documented
no-arena-reading contract by returning the existing no-boundary result when the
shift cannot be represented, while retaining current behavior for valid shifts.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dc7ecd40-03c3-404c-a57f-7a78dd6509bd

📥 Commits

Reviewing files that changed from the base of the PR and between e1d27b6 and 7f574f5.

📒 Files selected for processing (6)
  • changelog.d/7729-gc-zeal-allocation-pacing.md
  • crates/perry-runtime/src/gc/policy.rs
  • crates/perry-runtime/src/gc/telemetry.rs
  • crates/perry-runtime/src/gc/tests/triggers.rs
  • crates/perry-runtime/src/gc/zeal.rs
  • docs/src/internals/memory-model.md

| 64 | 1,291 | 52,357 | 1.1 s |

Row 0 reproduces the pre-fix 1:1 behaviour exactly on the shipped binary. Every row keeps `copying_minors == forced_collections` and `moved > 0`, so no stride degrades the instrument into non-moving sweeps. 4 KB rather than the faster 16/64 is deliberate — this is a correctness instrument, so the default errs toward sensitivity, still collecting once per ~15 loop iterations while being 14x cheaper than unpaced. The zeal-OFF path is untouched: the same workload without zeal is 4.49 s before and after.
Row 0 reproduces the pre-fix behaviour on the shipped binary: 283,857 forced collections for 283,852 polls — one per back-edge poll, plus a handful from the other safepoint zeal forces at (the outermost microtask-pump boundary, which the poll counter does not count). Near 1:1, not exactly. Every row keeps `copying_minors == forced_collections` and `moved > 0`, so no stride degrades the instrument into non-moving sweeps. 4 KB rather than the faster 16/64 is deliberate — this is a correctness instrument, so the default errs toward sensitivity, still collecting once per ~15 loop iterations while being 14x cheaper than unpaced. The zeal-OFF path is untouched: the same workload without zeal is 4.49 s before and after.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the parenthesis placement in both documentation copies.

The explanatory clause should start after boundary, not before the.

  • changelog.d/7729-gc-zeal-allocation-pacing.md#L23-L23: change at (the outermost microtask-pump boundary, which ... to at the outermost microtask-pump boundary (which ....
  • crates/perry-runtime/src/gc/zeal.rs#L215-L216: apply the same wording correction.
📍 Affects 2 files
  • changelog.d/7729-gc-zeal-allocation-pacing.md#L23-L23 (this comment)
  • crates/perry-runtime/src/gc/zeal.rs#L215-L216
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@changelog.d/7729-gc-zeal-allocation-pacing.md` at line 23, Correct the
parenthesis placement in changelog.d/7729-gc-zeal-allocation-pacing.md lines
23-23 and crates/perry-runtime/src/gc/zeal.rs lines 215-216: change “at (the
outermost microtask-pump boundary, which ...” to “at the outermost
microtask-pump boundary (which ...” in both documentation copies.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@changelog.d/7739-gc-pacing-snapshot-boundary.md`:
- Around line 15-17: Update the changelog text describing the snapshot’s
null/None value so it consistently states that no escalation boundary is
available when either major pacing is disabled or the growth calculation
overflows usize. Replace the conflicting claim that null means only pacing is
disabled, while preserving the existing explanation of the checked arithmetic
and trace-key semantics.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c6394a8a-f424-4e4b-87b6-b12381f29aae

📥 Commits

Reviewing files that changed from the base of the PR and between 7f574f5 and d76d87d.

📒 Files selected for processing (2)
  • changelog.d/7739-gc-pacing-snapshot-boundary.md
  • crates/perry-runtime/src/gc/policy.rs

Comment on lines +15 to +17
The fix is structural rather than a second correct formula. There is now **one** definition of the boundary, `major_pacing_escalation_threshold_bytes`: `arena_growth_full_escalation_due_inner` is literally `in_use >= it`, and the snapshot reports it verbatim, floor included. `None` means "no arena reading escalates" — either pacing is disabled (`PERRY_GC_MAJOR_PACING_FLOOR_MB=0`) or the growth term overflowed `usize`, which is the same statement about the world; the helper uses `checked_*` rather than `saturating_*` because saturating would report `usize::MAX` and then claim an arena of `usize::MAX` escalates, which the strict `>` clause never would.

The trace key follows the semantics: **`escalate_at_or_above_bytes`**, replacing `escalate_above_bytes`. The predicate's floor clause is a `>=`, and the old name was half of why the reported figure and the decision could disagree. `null` now means pacing is off. Nothing in `scripts/`, `.github/` or `docs/` consumed the old key.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Clarify the meaning of null.

Line 15 says None also represents a growth calculation that overflowed usize. Line 17 says null means only that pacing is disabled. These statements conflict. State that null means no escalation boundary is available because pacing is disabled or the calculation overflowed. (raw.githubusercontent.com)

Proposed wording
-  The trace key follows the semantics: **`escalate_at_or_above_bytes`**, replacing `escalate_above_bytes`. The predicate's floor clause is a `>=`, and the old name was half of why the reported figure and the decision could disagree. `null` now means pacing is off. Nothing in `scripts/`, `.github/` or `docs/` consumed the old key.
+  The trace key follows the semantics: **`escalate_at_or_above_bytes`**, replacing `escalate_above_bytes`. The predicate's floor clause is a `>=`, and the old name was half of why the reported figure and the decision could disagree. `null` means no escalation boundary is available because pacing is disabled or the growth calculation overflowed `usize`. Nothing in `scripts/`, `.github/` or `docs/` consumed the old key.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The fix is structural rather than a second correct formula. There is now **one** definition of the boundary, `major_pacing_escalation_threshold_bytes`: `arena_growth_full_escalation_due_inner` is literally `in_use >= it`, and the snapshot reports it verbatim, floor included. `None` means "no arena reading escalates" — either pacing is disabled (`PERRY_GC_MAJOR_PACING_FLOOR_MB=0`) or the growth term overflowed `usize`, which is the same statement about the world; the helper uses `checked_*` rather than `saturating_*` because saturating would report `usize::MAX` and then claim an arena of `usize::MAX` escalates, which the strict `>` clause never would.
The trace key follows the semantics: **`escalate_at_or_above_bytes`**, replacing `escalate_above_bytes`. The predicate's floor clause is a `>=`, and the old name was half of why the reported figure and the decision could disagree. `null` now means pacing is off. Nothing in `scripts/`, `.github/` or `docs/` consumed the old key.
The fix is structural rather than a second correct formula. There is now **one** definition of the boundary, `major_pacing_escalation_threshold_bytes`: `arena_growth_full_escalation_due_inner` is literally `in_use >= it`, and the snapshot reports it verbatim, floor included. `None` means "no arena reading escalates" — either pacing is disabled (`PERRY_GC_MAJOR_PACING_FLOOR_MB=0`) or the growth term overflowed `usize`, which is the same statement about the world; the helper uses `checked_*` rather than `saturating_*` because saturating would report `usize::MAX` and then claim an arena of `usize::MAX` escalates, which the strict `>` clause never would.
The trace key follows the semantics: **`escalate_at_or_above_bytes`**, replacing `escalate_above_bytes`. The predicate's floor clause is a `>=`, and the old name was half of why the reported figure and the decision could disagree. `null` means no escalation boundary is available because pacing is disabled or the growth calculation overflowed `usize`. Nothing in `scripts/`, `.github/` or `docs/` consumed the old key.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@changelog.d/7739-gc-pacing-snapshot-boundary.md` around lines 15 - 17, Update
the changelog text describing the snapshot’s null/None value so it consistently
states that no escalation boundary is available when either major pacing is
disabled or the growth calculation overflows usize. Replace the conflicting
claim that null means only pacing is disabled, while preserving the existing
explanation of the checked arithmetic and trace-key semantics.

Ralph Küpper added 3 commits August 10, 2026 07:30
…7733 follow-up)

`major_pacing_snapshot` recomputed the escalation boundary as
`baseline x growth` and discarded the floor (`let (_floor, growth_num) = ...`),
while `arena_growth_full_escalation_due` also rejects every reading below that
floor. Wherever the floor dominated the two disagreed -- most starkly before the
first full, where the trace reported `0` ("escalates at any size") for a
collector that escalates at 32 MB.

That snapshot exists precisely so the pacing subject can be asserted live in the
GC trace, so a probe that misreports its own subject is worse than none.

There is now one definition of the boundary
(`major_pacing_escalation_threshold_bytes`): the predicate is literally
`in_use >= it`, and the snapshot reports it verbatim, floor included. The trace
key follows the semantics -- `escalate_at_or_above_bytes`, `null` when
`PERRY_GC_MAJOR_PACING_FLOOR_MB=0` disables pacing outright.

Also: the ZealGuard test asserted the arm was taken and only narrated that it
was released, so a Drop that stopped releasing it would have left every later
test in the binary on the poll's slow path with the test still green.

Claude-Session: https://claude.ai/code/session_015JgLM9UWGa6WAMix7CvhQJ
@proggeramlug
proggeramlug force-pushed the fix/coderabbit-gc-followups branch from d76d87d to 2d20498 Compare August 10, 2026 05:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Cargo.toml`:
- Line 318: Revert the contributor-owned version metadata changes: restore
[workspace.package].version in Cargo.toml at lines 318-318 to 0.5.1432 and
Current Version in CLAUDE.md at lines 11-11 to 0.5.1432. Use the PR-keyed
changelog fragment for this change and do not update release metadata elsewhere.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f1537a40-7d70-42b3-b963-c64c4139adae

📥 Commits

Reviewing files that changed from the base of the PR and between d76d87d and 2d20498.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • CLAUDE.md
  • Cargo.toml

Comment thread Cargo.toml

[workspace.package]
version = "0.5.1432"
version = "0.5.1433"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Revert the contributor-owned version metadata changes.

Both files change the release version from 0.5.1432 to 0.5.1433, although this PR must not include a version bump.

  • Cargo.toml#L318-L318: restore [workspace.package].version to 0.5.1432.
  • CLAUDE.md#L11-L11: restore Current Version to 0.5.1432.

Use the PR-keyed changelog fragment for this change. The maintainer owns release metadata updates.

Based on learnings: contributors must not update release/version metadata themselves.

📍 Affects 2 files
  • Cargo.toml#L318-L318 (this comment)
  • CLAUDE.md#L11-L11
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` at line 318, Revert the contributor-owned version metadata
changes: restore [workspace.package].version in Cargo.toml at lines 318-318 to
0.5.1432 and Current Version in CLAUDE.md at lines 11-11 to 0.5.1432. Use the
PR-keyed changelog fragment for this change and do not update release metadata
elsewhere.

Sources: Coding guidelines, Learnings

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Merging as v0.5.1433

The snapshot bug is the important one, and the framing is right

A diagnostic that misreports the quantity it exists to prove is this repo's most expensive recurring failure — PERRY_GC_FORCE_EVACUATE inert for every gc()-driven test, the matrix's --pressure knob disabling the path it measured, moved= summing two collectors. #7733 added major_pacing_snapshot specifically so the pacing subject could be asserted live, and it named a boundary the collector does not use:

state predicate escalates at old snapshot said
no full yet 32 MB (floor) 0 — "any size"
baseline 4 MB, growth 2 32 MB (floor) 8 MB

The structural fix is what makes this landable rather than a second correct formula. There is now one definition — major_pacing_escalation_threshold_for, pure (config, state) → boundary — the predicate is literally in_use >= it, and the snapshot reports it verbatim. Two formulas that must agree is the thing that failed; one that both read cannot.

The checked_*-over-saturating_* reasoning is correct and worth keeping: saturating would report usize::MAX and then claim an arena of usize::MAX escalates, which the strict > clause never would. None genuinely means "no reading escalates", and pacing-disabled and overflow are the same statement about the world.

And the doc comment names why the divergence went untested: every reachable unit test sat below the floor, where two different formulas agree on the bool the predicate returns. That is the general lesson — a predicate that returns a bool can hide a wrong quantity indefinitely.

I verified the sabotage, and got it wrong first

Dropping the floor from the shared helper fails the_shipped_predicate_and_the_shipped_snapshot_read_one_boundary with left: Some(8388609), right: Some(33554432) — byte-for-byte the values you reported.

My first attempt appeared not to reproduce it, and that was my error: I filtered on major_pacing, which matches only #7733's two older tests and neither of the new ones. Worth recording because it is the same shape as the bug being fixed — I ran a check whose subject wasn't there and briefly believed the result.

The ZealGuard fix

zeal_holds_the_poll_word_armed_with_nothing_pending asserted the word was armed inside the scope and then only narrated the release in a comment. If Drop ever stopped giving the arm back, the process-global word would stay non-zero for the life of the test binary, every later test would silently take the poll's slow path, and this test would still pass. Now base + 1 inside, base after — matching the shape a_deferral_arms_the_poll_word_and_draining_disarms_it already uses.

On editing merged changelog fragments

Correcting changelog.d/7729-… in place is right, for the reason given: fragments are folded into the release notes at tag time, so the correction is what actually reaches a reader. Both corrections are real — the bytes_allocated / stride bound genuinely needs the positive-stride qualifier (=0 is a supported mode deliberately outside it), and 283,857 collections for 283,852 polls is near 1:1, not exactly, because the microtask-pump safepoint also forces without note_loop_poll_reached. Saying "near" is more useful than a round claim that a reader can falsify from the table two lines up.

The memory-model.md caveat still saying "default off since #7161" twenty lines under a line saying #7721 flipped it is exactly the drift that produced #7690.

Gates 21/21.

@proggeramlug
proggeramlug merged commit dc6721c into main Aug 10, 2026
1 of 16 checks passed
@proggeramlug
proggeramlug deleted the fix/coderabbit-gc-followups branch August 10, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant