Skip to content

Fix GC Map and Set teardown leaks - #6387

Merged
proggeramlug merged 2 commits into
mainfrom
fix/gc-map-set-teardown-leak
Jul 14, 2026
Merged

Fix GC Map and Set teardown leaks#6387
proggeramlug merged 2 commits into
mainfrom
fix/gc-map-set-teardown-leak

Conversation

@TheHypnoo

@TheHypnoo TheHypnoo commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

Release registry-owned Map and Set side buffers at thread and executable teardown so arena destruction cannot orphan external allocations.

Changes

  • Store Map and Set side-buffer ownership in thread-local owner records that survive GC header movement.
  • Finalize owner records exactly once during collection, thread teardown, explicit process exit, and fatal rejection exit.
  • Preserve external live-byte accounting while draining registries.
  • Share the platform termination primitive so fatal exits bypass atexit after GC teardown, matching process.exit() safety.
  • Emit executable-only teardown after exit callbacks and rejection reporting; host and dynamic-library returns remain unchanged.
  • Add runtime, copying-GC, exactly-once, and emitted-IR regression coverage.

Related issue

n/a

Test plan

Before the fix, the exact local Linux LeakSanitizer reproduction passed its Rust test but exited with a 6,144-byte leak across 128 allocations. After the fix and the review adjustments, the same command exits successfully with leak detection enabled and no sanitizer report.

  • cargo build --release clean (via the parity runner)
  • cargo test --workspace --exclude perry-ui-ios --exclude perry-ui-tvos --exclude perry-ui-watchos --exclude perry-ui-gtk4 --exclude perry-ui-android --exclude perry-ui-windows passes
  • (if user-facing) Added or updated a test under test-files/ or a #[test] in the affected crate
  • (if CLI / stdlib / runtime API changed) Updated docs/src/ (not applicable; internal teardown contract only)
  • (if touching a platform UI backend) Built -p perry-ui-<backend> locally on that platform (not applicable)

Additional verification:

  • RUST_TEST_THREADS=1 cargo test --lib -p perry-runtime -- --test-threads=1 — 1,312 passed, 1 ignored
  • cargo test -p perry-codegen --lib codegen::entry::tests — 2 passed
  • cargo test -p perry-codegen --test shadow_slot_hygiene — 9 passed
  • cargo test -p perry --test issue_6185_thread_worker_closure_guards — 12 passed
  • python3 scripts/gc_store_site_inventory.py --self-test
  • python3 scripts/gc_store_site_inventory.py
  • python3 scripts/addr_class_inventory.py --self-test
  • python3 scripts/addr_class_inventory.py
  • cargo fmt --all -- --check
  • git diff --check
  • ./run_parity_tests.sh --filter test_gap_fs_fd_2749 — passed four consecutive local runs after one CI mismatch

Screenshots / output

Before: LeakSanitizer reported 4,096 bytes from 64 Map allocations and 2,048 bytes from 64 Set allocations.

After: the exact test exits 0 with detect_leaks=1 and no AddressSanitizer or LeakSanitizer findings.

Checklist

  • I have NOT bumped the workspace version or edited CLAUDE.md / CHANGELOG.md (maintainer handles these at merge)
  • My commits follow the loose feat: / fix: / docs: / chore: prefix convention used in the log
  • I've read CONTRIBUTING.md and agree to the Code of Conduct

Summary by CodeRabbit

  • Bug Fixes
    • Improved cleanup of per-thread Map/Set side-allocation memory at thread exit and at the real process-exit boundary.
    • Ensured Map/Set side allocations stay correctly tracked through garbage-collection relocation and capacity growth.
    • Prevented repeated cleanup from releasing the same allocation more than once.
    • Improved fatal-exit and unhandled rejection handling to run the same current-thread cleanup before termination.
  • Tests
    • Added coverage for process-exit cleanup, thread teardown, relocation, growth, and “release exactly once” behavior, including generated-code IR checks.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a05f258-8716-4d28-be1a-a5d5da8bc102

📥 Commits

Reviewing files that changed from the base of the PR and between cbcda22 and eebebcf.

📒 Files selected for processing (1)
  • crates/perry-codegen/src/codegen/entry.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry-codegen/src/codegen/entry.rs

📝 Walkthrough

Walkthrough

Map and Set side-allocation registries now own buffer metadata through growth, relocation, finalization, and thread teardown. Runtime exit paths expose and invoke cleanup, executable codegen places it at process exit, and tests validate ownership transfer and exactly-once release.

Changes

GC side-allocation teardown

Layer / File(s) Summary
Map and Set ownership records
crates/perry-runtime/src/map.rs, crates/perry-runtime/src/set.rs, crates/perry-runtime/src/gc/mod.rs
Map and Set registries now store buffer pointers and capacities, update records during growth and GC relocation, deallocate owned buffers during finalization, and expose current-thread release helpers.
GC relocation and teardown validation
crates/perry-runtime/src/gc/tests/*
Tests verify side-allocation transfer across copying collection, release on thread exit, idempotent cleanup, and ownership correctness after growth.
Process and executable exit cleanup
crates/perry-runtime/src/process/*, crates/perry-runtime/src/promise/rejection.rs, crates/perry-codegen/src/codegen/entry*, crates/perry-codegen/src/runtime_decls/strings.rs
Runtime termination paths and generated executable exit code release current-thread side allocations; dylib and host-return paths omit process-owned cleanup, with IR tests covering ordering and presence.

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

Sequence Diagram(s)

sequenceDiagram
  participant ExecutableEntry
  participant RuntimeGC
  participant MapRegistry
  participant SetRegistry
  ExecutableEntry->>RuntimeGC: js_gc_release_current_thread_collection_side_allocations()
  RuntimeGC->>MapRegistry: release_current_thread_map_side_allocations()
  RuntimeGC->>SetRegistry: release_current_thread_set_side_allocations()
  RuntimeGC-->>ExecutableEntry: return after cleanup
Loading

Possibly related PRs

  • PerryTS/perry#6034: Updates Map/Set external side-allocation accounting and registry-driven finalization, which relates to this PR’s ownership and teardown changes.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: fixing Map and Set teardown leaks.
Description check ✅ Passed The description follows the template well, covering summary, changes, related issue, test plan, output, and checklist.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gc-map-set-teardown-leak

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: 2

🧹 Nitpick comments (2)
crates/perry-runtime/src/map.rs (1)

644-659: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Bulk registry clear on thread teardown skips GC external-side-byte accounting. Both release_current_thread_map_side_allocations and release_current_thread_set_side_allocations free their side buffers via RefCell::clear()'s implicit Drop, but neither calls crate::gc::gc_note_external_side_free the way drop_map_index/finalize_map_side_allocation_for_gc (map.rs) and drop_set_index/finalize_set_side_allocation_for_gc/test_clear_set_roots (set.rs) do. GC_EXTERNAL_SIDE_LIVE_BYTES is left stale for whatever remains of the thread's lifetime after this call. Today this is harmless — the only caller, js_gc_release_current_thread_collection_side_allocations, runs immediately before libc::_exit/std::process::exit — but it's a latent foot-gun if this helper is ever invoked without an immediate thread/process exit.

  • crates/perry-runtime/src/map.rs#L644-L659: in release_current_thread_map_side_allocations, drain MAP_REGISTRY and call crate::gc::gc_note_external_side_free(allocation.byte_len()) per entry before dropping, mirroring test_clear_set_roots's pattern.
  • crates/perry-runtime/src/set.rs#L460-L473: apply the same drain-and-notify fix to release_current_thread_set_side_allocations.
🤖 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/map.rs` around lines 644 - 659, Update
release_current_thread_map_side_allocations in
crates/perry-runtime/src/map.rs:644-659 to drain MAP_REGISTRY, call
crate::gc::gc_note_external_side_free with each allocation’s byte_len(), then
drop the drained allocations before clearing the indexes. Apply the same
drain-and-notify behavior to release_current_thread_set_side_allocations in
crates/perry-runtime/src/set.rs:460-473, preserving the existing index cleanup.
crates/perry-runtime/src/set.rs (1)

259-358: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

test_clear_set_roots correctly drains-and-accounts; contrast with release_current_thread_set_side_allocations below.

Good pattern here: explicit drain() + gc_note_external_side_free per allocation before drop, unlike the bulk clear() at Line 470. See consolidated comment for the cross-file inconsistency.

🤖 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/set.rs` around lines 259 - 358, Update
release_current_thread_set_side_allocations to drain each allocation from the
registry, call gc_note_external_side_free with its byte length, and then drop
it, matching the per-allocation cleanup pattern in test_clear_set_roots instead
of bulk-clearing without accounting.
🤖 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/process/env_misc.rs`:
- Around line 120-127: Update exit_after_current_thread_collection_teardown to
use the same platform-specific no-atexit termination helper as js_process_exit
instead of std::process::exit, while preserving release of current-thread
collection side allocations before termination. Factor the hard-exit logic into
a shared helper if it is currently embedded in js_process_exit, then call that
helper from both paths.

In `@plans/008-fix-gc-map-set-teardown-leak.md`:
- Around line 257-266: Update the focused-runner expectations in this plan,
including the corresponding done criteria, to require the current 462-test
inventory after adding the three teardown tests. Keep the instruction to inspect
inventory drift and prohibit hard-coded exclusions; only document an alternative
if the new tests are intentionally outside Plan 001’s runner.

---

Nitpick comments:
In `@crates/perry-runtime/src/map.rs`:
- Around line 644-659: Update release_current_thread_map_side_allocations in
crates/perry-runtime/src/map.rs:644-659 to drain MAP_REGISTRY, call
crate::gc::gc_note_external_side_free with each allocation’s byte_len(), then
drop the drained allocations before clearing the indexes. Apply the same
drain-and-notify behavior to release_current_thread_set_side_allocations in
crates/perry-runtime/src/set.rs:460-473, preserving the existing index cleanup.

In `@crates/perry-runtime/src/set.rs`:
- Around line 259-358: Update release_current_thread_set_side_allocations to
drain each allocation from the registry, call gc_note_external_side_free with
its byte length, and then drop it, matching the per-allocation cleanup pattern
in test_clear_set_roots instead of bulk-clearing without accounting.
🪄 Autofix (Beta)

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: 187a247a-ba86-44be-9ff3-673ebeff2329

📥 Commits

Reviewing files that changed from the base of the PR and between b79dc9b and 08532c3.

📒 Files selected for processing (13)
  • crates/perry-codegen/src/codegen/entry.rs
  • crates/perry-codegen/src/codegen/entry/tests.rs
  • crates/perry-codegen/src/runtime_decls/strings.rs
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/tests/copying.rs
  • crates/perry-runtime/src/gc/tests/mod.rs
  • crates/perry-runtime/src/gc/tests/teardown.rs
  • crates/perry-runtime/src/map.rs
  • crates/perry-runtime/src/process.rs
  • crates/perry-runtime/src/process/env_misc.rs
  • crates/perry-runtime/src/promise/rejection.rs
  • crates/perry-runtime/src/set.rs
  • plans/008-fix-gc-map-set-teardown-leak.md

Comment thread crates/perry-runtime/src/process/env_misc.rs
Comment thread plans/008-fix-gc-map-set-teardown-leak.md Outdated
@TheHypnoo
TheHypnoo force-pushed the fix/gc-map-set-teardown-leak branch from 08532c3 to cbcda22 Compare July 13, 2026 19:08
@proggeramlug
proggeramlug merged commit f5083c5 into main Jul 14, 2026
26 checks passed
@proggeramlug
proggeramlug deleted the fix/gc-map-set-teardown-leak branch July 14, 2026 13:44
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.

2 participants