Skip to content

fix(gc): bound and drain recycled block pool - #7889

Merged
proggeramlug merged 3 commits into
mainfrom
fix/7875-block-pool-pressure
Aug 11, 2026
Merged

fix(gc): bound and drain recycled block pool#7889
proggeramlug merged 3 commits into
mainfrom
fix/7875-block-pool-pressure

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Closes #7875

What changed

  • enforce one process-wide recycled-block allowance instead of allowing every perry/thread agent to retain 64 MiB
  • scale that allowance to one eighth of PERRY_GC_HEAP_LIMIT / the device budget, with the historical 64 MiB desktop cap unchanged
  • make critical pressure and allocation-failure full collections drain the current thread's pool after arena reclamation and before allocator trimming; deferred pressure keeps that drain request sticky through intervening minors
  • split arena release telemetry into removed, pooled, explicitly pool-drained, and actually deallocated bytes instead of reporting pooled mappings as returned to the allocator

Reproduction and coverage

A regression test seeded one 1 MiB recycled block, called js_gc_memory_pressure(2), and failed on the old implementation with 1,048,576 bytes still pooled. Coverage now includes synchronous, shadow-frame-deferred, and suppressed critical pressure; allocation-failure recovery; small-budget cap scaling; four concurrent owners sharing one atomic cap; pool/thread teardown; release accounting; incremental old-gen publication; and trace JSON.

Validation

  • cargo test -p perry-runtime block_pool — 7 passed
  • focused allocation-failure, release-accounting, incremental-reclaim, and telemetry tests — passed
  • cargo test -p perry-runtime before the final rebase — 2,131 passed, 4 ignored
  • on rebased main, two parallel full-suite runs each moved the only failure to a different unrelated shared-state/timing test (promise::keyed_table then promote_in_place); both passed immediately in isolation and all memory(gc): critical pressure and device budgets do not drain or bound the per-thread 64 MiB block pool #7875 tests passed in both runs
  • diagnostics-feature build, address/root/test-registration/TLS audits, formatting, diff check, and file-size gate — passed (current main reports the unrelated stale-but-improved dyn_index.rs address-class baseline)

M1 mini A/B

Same archived compiler for both arms; matching base/fix runtime + stdlib wrapper archives; two sessions in reversed order, 7 repeats per session (14 samples/arm):

probe wall RSS peak RSS
nursery churn +0.415% +0.217% +0.214%
large live set +0.027% -0.078% -0.039%
grow then churn -0.208% 0.000% -0.089%

Heap used, heap total, GC cycle counts, copied/promoted object and byte counts, and freed bytes were identical in both arms for every probe.

Summary by CodeRabbit

  • New Features

    • Improved memory management under critical pressure by draining recycled memory blocks during full collection.
    • Recycled block capacity is shared across the process and adapts to available heap budgets.
    • Emergency allocation recovery can release pooled memory before retrying.
  • Bug Fixes

    • Improved reclamation behavior for constrained devices and allocation failures.
    • Preserved memory recovery when collection is deferred or temporarily suppressed.
  • Diagnostics

    • Added detailed reporting for pooled, released, deallocated, reclaimed, and drained memory.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The runtime replaces the fixed per-thread arena block-pool cap with process-wide, heap-budget-aware accounting. Arena reclamation separates removed, pooled, and deallocated blocks. Critical pressure and emergency recovery drain pooled blocks, with expanded GC telemetry and tests.

Changes

Arena block-pool lifecycle

Layer / File(s) Summary
Pool accounting, capacity, and drain APIs
changelog.d/7889-block-pool-pressure.md, crates/perry-runtime/src/arena/block.rs, crates/perry-runtime/src/arena/mod.rs, crates/perry-runtime/src/gc/heap_budget.rs, crates/perry-runtime/src/arena/tests.rs
The pool uses process-wide atomic reservations, heap-budget-scaled limits, explicit and deferred drains, and release classification. Tests cover shared capacity and allocation-failure recovery.
Arena reclamation and release statistics
crates/perry-runtime/src/arena/page_meta.rs, crates/perry-runtime/src/arena/quarantine.rs, crates/perry-runtime/src/arena/reset.rs, crates/perry-runtime/src/arena/walk.rs, crates/perry-runtime/src/arena/tests.rs
Arena reset paths release blocks through the shared helper and track removed, pooled, and deallocated counts and bytes across nursery, survivor, and old-generation paths.
GC pressure, draining, and telemetry
crates/perry-runtime/src/gc/copying.rs, crates/perry-runtime/src/gc/cycle.rs, crates/perry-runtime/src/gc/mod.rs, crates/perry-runtime/src/gc/oldgen.rs, crates/perry-runtime/src/gc/pressure.rs, crates/perry-runtime/src/gc/telemetry.rs, crates/perry-runtime/src/gc/tests/*
Critical pressure requests pool draining, emergency reclaim drains before allocation retry, full cycles service deferred drains, and GC JSON reports pooled and drained statistics. Tests cover pressure, deferral, suppression, budgets, reclaim, and serialization.

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

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: bounding and draining the recycled block pool.
Description check ✅ Passed The description explains the change, links issue #7875, lists coverage, and documents validation, although it uses headings different from the template.
Linked Issues check ✅ Passed The implementation addresses issue #7875 through process-wide budget limits, pressure and failure drains, deferred handling, telemetry, and comprehensive tests.
Out of Scope Changes check ✅ Passed The code, telemetry, changelog, and tests are directly related to bounding and draining the recycled block pool described in issue #7875.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/7875-block-pool-pressure

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.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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 `@crates/perry-runtime/src/arena/reset.rs`:
- Around line 891-902: Update SurvivorArenaReclaimDeadBlocksState::step so each
successful state0.step() or state1.step() call immediately merges that child’s
counter delta, preserving removed_* and pooled_* statistics across multiple
reclamation slices. Add a regression test using multiple survivor blocks and a
one-unit budget to verify cumulative statistics.
🪄 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: 56978740-9c84-4076-b98a-ca872ed293d4

📥 Commits

Reviewing files that changed from the base of the PR and between 564bd99 and 56b4121.

📒 Files selected for processing (20)
  • changelog.d/7889-block-pool-pressure.md
  • crates/perry-runtime/src/arena/block.rs
  • crates/perry-runtime/src/arena/mod.rs
  • crates/perry-runtime/src/arena/page_meta.rs
  • crates/perry-runtime/src/arena/quarantine.rs
  • crates/perry-runtime/src/arena/reset.rs
  • crates/perry-runtime/src/arena/tests.rs
  • crates/perry-runtime/src/arena/walk.rs
  • crates/perry-runtime/src/gc/copying.rs
  • crates/perry-runtime/src/gc/cycle.rs
  • crates/perry-runtime/src/gc/heap_budget.rs
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/oldgen.rs
  • crates/perry-runtime/src/gc/pressure.rs
  • crates/perry-runtime/src/gc/telemetry.rs
  • crates/perry-runtime/src/gc/tests/block_pool_pressure.rs
  • crates/perry-runtime/src/gc/tests/incremental_sweep_reclaim.rs
  • crates/perry-runtime/src/gc/tests/mod.rs
  • crates/perry-runtime/src/gc/tests/oldgen.rs
  • crates/perry-runtime/src/gc/tests/triggers.rs

Comment thread crates/perry-runtime/src/arena/reset.rs
@proggeramlug
proggeramlug merged commit eb0f367 into main Aug 11, 2026
11 of 19 checks passed
@proggeramlug
proggeramlug deleted the fix/7875-block-pool-pressure branch August 11, 2026 21:25

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/perry-runtime/src/arena/tests.rs (1)

1258-1275: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the drain and process-wide census.

force_next_block_alloc_failure is consumed by the injectable first allocation, and emergency reclaim requests the pool drain. block_pool_bytes_for_test() checks only the thread-local pool. Capture the initial drain and process-wide counts, then assert the drain census includes the removed bytes and the process-wide reservation returns to its initial value.

🤖 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/arena/tests.rs` around lines 1258 - 1275, Update
allocation_failure_recovery_drains_mismatched_pooled_blocks to capture the
initial drain census and process-wide reservation count before setup. After
reserve_arena_block(BLOCK_SIZE + 1), assert the drain census accounts for the
removed pooled block bytes and that the process-wide reservation count has
returned to its initial value, while retaining the existing thread-local
assertion.
🧹 Nitpick comments (1)
crates/perry-runtime/src/arena/tests.rs (1)

1207-1256: 🩺 Stability & Availability | 🔵 Trivial

Run this test with serialized runtime tests.

The test checks process-wide pool reservations while other runtime tests can use the same process-global state. Parallel test execution can change the reservation before the final zero check.

Use RUST_TEST_THREADS=1 for focused and full runtime test commands, or use the suite's global test lock if one exists.

As per coding guidelines, runtime tests are not parallel-safe because they share process-global side tables; run them with RUST_TEST_THREADS=1.

🤖 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/arena/tests.rs` around lines 1207 - 1256, Run the
runtime test suite, including
block_pool_cap_is_process_wide_across_live_threads, with serialized test
execution by setting RUST_TEST_THREADS=1 in focused and full test commands, or
use the suite’s existing global test lock if available. Keep the test’s
process-wide reservation assertions unchanged.

Source: Coding guidelines

🤖 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 `@crates/perry-runtime/src/arena/tests.rs`:
- Around line 163-166: Update the test using run_with_fresh_arenas so its
pool-cap precondition is explicit: configure PERRY_GC_HEAP_LIMIT to a budget of
at least 16 MB before collecting stats, or change the assertions to validate the
pooled/deallocated partition for smaller budgets. Preserve the existing block
and byte accounting expectations.

---

Outside diff comments:
In `@crates/perry-runtime/src/arena/tests.rs`:
- Around line 1258-1275: Update
allocation_failure_recovery_drains_mismatched_pooled_blocks to capture the
initial drain census and process-wide reservation count before setup. After
reserve_arena_block(BLOCK_SIZE + 1), assert the drain census accounts for the
removed pooled block bytes and that the process-wide reservation count has
returned to its initial value, while retaining the existing thread-local
assertion.

---

Nitpick comments:
In `@crates/perry-runtime/src/arena/tests.rs`:
- Around line 1207-1256: Run the runtime test suite, including
block_pool_cap_is_process_wide_across_live_threads, with serialized test
execution by setting RUST_TEST_THREADS=1 in focused and full test commands, or
use the suite’s existing global test lock if available. Keep the test’s
process-wide reservation assertions unchanged.
🪄 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: 8771c320-102f-4871-ac8f-3dee67168331

📥 Commits

Reviewing files that changed from the base of the PR and between 56b4121 and 65602c8.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/arena/reset.rs
  • crates/perry-runtime/src/arena/tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-runtime/src/arena/reset.rs

Comment on lines +163 to +166
assert_eq!(stats.pooled_blocks, 2);
assert_eq!(stats.pooled_bytes, stats.removed_bytes);
assert_eq!(stats.deallocated_blocks, 0);
assert_eq!(stats.deallocated_bytes, 0);

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="crates/perry-runtime/src/arena/tests.rs"

ast-grep outline "$file" --items all --type function
sed -n '130,175p' "$file"
rg -n -C 6 \
  'run_with_fresh_arenas|block_pool_cap_bytes|PERRY_GC_HEAP_LIMIT|heap_budget' \
  "$file" \
  crates/perry-runtime/src/arena/block.rs \
  crates/perry-runtime/src/gc/heap_budget.rs

Repository: PerryTS/perry

Length of output: 42118


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- test helpers and constants ---'
sed -n '1,115p' crates/perry-runtime/src/arena/tests.rs
echo '--- target test ---'
sed -n '140,170p' crates/perry-runtime/src/arena/tests.rs
echo '--- reset/release implementation ---'
sed -n '180,270p' crates/perry-runtime/src/arena/block.rs
echo '--- pool accounting and budget helpers ---'
sed -n '120,155p' crates/perry-runtime/src/gc/heap_budget.rs
rg -n -C 8 \
  'BLOCK_SIZE|BLOCK_POOL_BYTES|BLOCK_POOL_PROCESS_BYTES|block_pool_(put|cap|clear|reset)|RUST_TEST_THREADS|serial|PERRY_GC_HEAP_LIMIT' \
  crates/perry-runtime/src/arena crates/perry-runtime/src/gc

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
BLOCK_SIZE = 1024 * 1024

def budget_scaled(budget, default=64 * 1024 * 1024, num=1, den=8, floor=BLOCK_SIZE):
    if budget is None:
        return default
    # Match the documented min(default, budget*num/den), then floor.
    return max(floor, min(default, budget * num // den))

print("required_bytes_for_two_removed_blocks =", 2 * BLOCK_SIZE)
for mb in (1, 8, 15, 16, 17, 512, 1024, None):
    budget = None if mb is None else mb * BLOCK_SIZE
    cap = budget_scaled(budget)
    print(f"budget_mb={mb!s:>4} pool_cap={cap // BLOCK_SIZE} blocks "
          f"admits_two={cap >= 2 * BLOCK_SIZE}")

print("source facts:")
print("run_with_fresh_arenas_sets_budget = False")
print("BLOCK_POOL_PROCESS_BYTES_is_static_process_global = True")
print("BLOCK_POOL_is_thread_local = True")
print("thread_local_pool_drop_subtracts_process_counter = False")
PY

echo '--- exact budget scaling implementation ---'
sed -n '85,125p' crates/perry-runtime/src/gc/heap_budget.rs
echo '--- thread-local pool type and teardown declarations ---'
sed -n '105,155p' crates/perry-runtime/src/arena/block.rs
rg -n -C 4 \
  'impl Drop for BlockPool|drain_block_pool_if_requested|request_block_pool_drain|BLOCK_POOL_PROCESS_BYTES' \
  crates/perry-runtime/src/arena crates/perry-runtime/src/gc

Repository: PerryTS/perry

Length of output: 12939


Make the pool-cap precondition explicit.

run_with_fresh_arenas does not set PERRY_GC_HEAP_LIMIT. For budgets below 16 MB, gc_block_pool_cap_bytes() admits only one BLOCK_SIZE, but the test requires two pooled blocks. Pin a budget that admits both blocks, or assert the pooled/deallocated partition.

🤖 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/arena/tests.rs` around lines 163 - 166, Update the
test using run_with_fresh_arenas so its pool-cap precondition is explicit:
configure PERRY_GC_HEAP_LIMIT to a budget of at least 16 MB before collecting
stats, or change the assertions to validate the pooled/deallocated partition for
smaller budgets. Preserve the existing block and byte accounting expectations.

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.

memory(gc): critical pressure and device budgets do not drain or bound the per-thread 64 MiB block pool

1 participant