feat(gc): per-module raw-handle ceilings — new code is clean by construction (Layer 3) - #7457
Conversation
…ruction
The Layer 3 debt was a single global total, which cannot make the
discipline non-optional. A total is blind to debt MOVING between modules,
and it lets a brand-new file start dirty as long as something else got
cleaner. It also has no finish line: it drifts down, it never converges.
595 of the 705 runtime modules already carry zero bare reads. Inverting
the ratchet turns that into the enforced default: list the 110 modules
PERMITTED to carry debt, with a per-module ceiling, and everything else
must be at zero. Three directions, all closed:
1. An unlisted module with any bare read fails. New code therefore has
to use RuntimeHandle::across_{mut,const,nanbox}.
2. A listed module over its ceiling fails.
3. A listed module that reaches ZERO fails until its line is deleted --
the same matching-nothing rule as gc_root_dominance_allowlist.json,
which is what makes a cleanup permanent instead of re-permitting the
debt on the next edit.
The per-module checks run BEFORE the global total, deliberately: the
specific diagnostic names the file and the fix, and the total check
returning first would suppress it for the most common failure.
All three rules and the clean case are asserted in --self-test, and the
self-test fails when check_per_module is stubbed out.
|
Warning Review limit reached
Next review available in: 4 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
) * gc: clean three runtime modules to zero raw-handle debt First use of #7457's per-module ceilings, and the mechanism did its job: converting the three sites left the gate RED with array/from_concat.rs: ceiling of 1 matches nothing -- the module is clean (or gone). DELETE its line so the cleanup cannot be undone. until the lines were removed. That is rule 3 working -- without it a cleaned module keeps its allowance and silently re-permits the debt. array/from_concat.rs js_array_grow -> across_const object/array_object_ops.rs js_string_coerce -> across_mut symbol.rs gc_malloc -> across_mut Each is the canonical shape: an allocating call immediately above a re-read of a rooted receiver. Semantically identical; the ordering is now structural rather than conventional. 110 -> 107 modules, 1002 -> 999 sites. Those three can never regress: they are no longer on the list, so rule 1 holds them at zero. * docs: changelog fragment for #7458 --------- Co-authored-by: Ralph Küpper <ralph@skelpo.com>
Layer 3 of the engine plan (#7294). The plan's goal for this layer is that
RuntimeHandleScopestops being optional; a single global debt total cannot express that, so this changes the shape of the gate rather than its number.Why the total was the wrong instrument
raw_handle_debt.pytracked one number. That is blind in three ways:It also went red twice today (#7424, #7451) precisely because a correct rooting fix spends budget, and the only remedy is to find an unrelated conversion.
The inversion
595 of the 705 runtime modules already carry zero bare reads. So the default should be zero, not "whatever the total allows".
scripts/raw_handle_debt_files.txtnow lists the 110 modules permitted to carry debt, each with a ceiling. Three rules, all closed:across_{mut,const,nanbox}Rule 3 is the one that makes cleanup permanent — it is the same matching-nothing rule as
gc_root_dominance_allowlist.json. Without it a cleaned module keeps its line forever and silently re-permits the debt on the next edit.Two details that are easy to get wrong
Ordering. The per-module checks run before the global total. I had it the other way first and my own rule-2 test printed nothing: the total check returned first, so the specific diagnostic ("
symbol.rs: 2 bare reads exceeds its ceiling of 1") never appeared for the most common failure shape. The general message does not name the file or the fix.Self-test. All three rules and the clean case are asserted in
--self-test, in the live path — my first attempt appended them after an earlyreturnand they never ran, which the original self-test's own output made obvious. Stubbingcheck_per_moduletoreturn []now produces:Each rule was also exercised end-to-end against the real tree:
Scope
Seeded at today's state — 1002 sites, 110 modules — so it is green on merge and changes no runtime code.
--updaterewrites both the total and the ceilings, and still refuses to raise the total.This does not finish Layer 3.
RuntimeHandleScopeis still optional inside those 110 modules, and the RFC's end state is that the raw accessor is unreachable rather than merely counted. What it does is stop the other 595 from ever joining them, and give the remaining work a finish line: the list can only shrink.