fix(runtime): a live-index read inside forEach defers the squeeze instead of shifting the walk - #9561
fix(runtime): a live-index read inside forEach defers the squeeze instead of shifting the walk#9561proggeramlug wants to merge 2 commits into
Conversation
…tead of shifting the walk PerryTS#9504 made the array-like `map[i]` / `set[i]` read a live-index accessor: it squeezes tombstones so raw index == live index and never hands out a hole. `forEach` walks the raw entries with a counter that the delete-path squeeze defers around (the walk registers itself) — but the accessor's squeeze had no such guard. A callback that deleted already-visited entries and then read `map[j]` compacted the buffer under the walk's counter, shifting the survivors below it: with two earlier entries deleted, two later ones were never visited (18 of 20, Map and Set). While a walk is active the accessor now defers the squeeze exactly as the delete path does and resolves the live index by stepping over the tombstones (O(idx), on a path that is rare by construction); the outermost walk's completion performs the deferred squeeze as before. Outside a walk the accessor squeezes as PerryTS#9504 specified, and PerryTS#9504's `a_tombstoned_collection_never_hands_a_hole_to_an_indexed_read` stays green. The for…of fast path is unaffected: its cursor rebases through the compaction log (PerryTS#9513), so a squeeze under it was already exact. Found by the automated review on PerryTS#9513, reproduced on merged main. Tests: `a_live_index_read_inside_foreach_defers_the_squeeze_and_skips_nothing` for Map and Set (every entry visited once; live values read mid-walk; layout left alone during the walk, squeezed on completion; the accessor still squeezes outside a walk) and `test_gap_foreach_live_index_read_no_skip.ts` (single and nested walks), node-differential. perry-runtime 3018 passed / 0 failed (release, single-threaded); PerryTS#9504's and PerryTS#9513's fixtures still match node; cargo fmt --all --check clean; no clippy warning in the touched files.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (6)
Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughMap and Set live-index accessors now defer tombstone compaction during active ChangesforEach live-index traversal
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to The change defers live-index compaction during active forEach walks and resolves indexes without skipping entries; no actionable merge-blocking risk remains after the stated validation. Sequence Diagram(s)sequenceDiagram
participant MapSetForEach
participant ForEachCallback
participant LiveIndexAccessor
MapSetForEach->>ForEachCallback: invoke callback
ForEachCallback->>LiveIndexAccessor: delete earlier entries and read live index
LiveIndexAccessor->>LiveIndexAccessor: step over tombstones
LiveIndexAccessor-->>ForEachCallback: return requested live entry
MapSetForEach->>MapSetForEach: compact after outermost walk
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 5 files. (1 skipped: 1 unsupported.) Full details: Description checkExplanation The description is detailed and on-topic. It explains the bug, fix, scope, regression coverage, and validation results. It does not use every template heading, such as Related issue, Screenshots / output, and Checklist, but the missing sections are non-critical for this change.
✨ Finishing Touches🧪 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 |
|
Landed via merge train #9563 (rebase-merge, authorship preserved). |
Summary
#9504made the array-likemap[i]/set[i]read a live-index accessor: it squeezes tombstones so raw index == live index and never hands out a hole.forEachwalks the raw entries with a counter that the delete-path squeeze defers around (the walk registers itself) — but the accessor's own squeeze had no such guard. A callback that deletes already-visited entries and then readsmap[j]compacted the buffer under the walk's counter, shifting the survivors below it:Found by the automated review on #9513 and reproduced on merged
main. Thefor…offast path is unaffected — its cursor rebases through the compaction log (#9513), so a squeeze under it was already exact.Fix
While a
forEachwalk is active, the live-index accessor defers the squeeze exactly as the delete path does and resolves the live index by stepping over the tombstones (O(idx), on a path that is rare by construction: an array-like read on a collection during its own walk). The outermost walk's completion performs the deferred squeeze as before. Outside a walk the accessor squeezes as #9504 specified, and #9504'sa_tombstoned_collection_never_hands_a_hole_to_an_indexed_readstays green.Tests / validation
a_live_index_read_inside_foreach_defers_the_squeeze_and_skips_nothingfor Map and Set: every entry visited exactly once, live values read mid-walk are correct, the layout is left alone during the walk and squeezed on completion, and the accessor still squeezes outside a walk.test-files/test_gap_foreach_live_index_read_no_skip.ts: single and nested walks, node-differential (the reproducer goes 18/20 → 20/20).RUST_TEST_THREADS=1 cargo test --release -p perry-runtime --lib: 3018 passed, 0 failed. fix(runtime): the hole-leak family — Set/Map raw reads, pop, typeof/String, param guards, console.table; util.format %s/%o policy (#9462, #9463) #9504's and fix(runtime,hir,codegen): Map/Set mutation during for…of is O(1) per step and never skips an entry #9513's fixtures still match node.cargo fmt --all --checkclean; no clippy warning in the touched files.Summary by CodeRabbit
Bug Fixes
MapandSetforEachiteration so deleting previously visited entries no longer causes later entries to be skipped.forEachcallbacks, including nested walks.Tests