Re-mark the region tracker at the merged order when freeing pages#1294
Merged
Conversation
free_helper() marked the region tracker free only at the freed page's own order. But freeing a page can merge it with its buddies into a block of a higher order, and the region tracker is a per-order index of which regions may have free space. Once a region had been marked full at some order K (mark_full, when an allocation of order K failed on it), only a free at order >= K would clear that bit, since mark_free(order) clears orders 0..=order. A free at a lower order that merged up into a free order-K block left the bit set. With the bit stale, find_free(K) skipped the region even though it held a free block, so allocations grew the file instead of reusing the space. compact()'s allocate_lowest probe was likewise misdirected to a higher page and hit its "new >= old" early break, terminating before reclaiming reclaimable space -- a plausible root cause for "compact() doesn't shrink my file" reports. Full repair rebuilt the tracker and healed it; quick-repair persisted the staleness across restarts. The buddy allocator itself stayed exact, so this never corrupted data. Have BuddyAllocator::free() return the order of the resulting block -- the order at which buddy merging stopped -- and mark the region tracker free at that order. This clears the tracker's stale higher-order bits after a merge without re-scanning every order to recompute the region's highest free order on each free. Add a regression test that fills a region, frees it so the pages merge back up, and checks that the next allocation reuses the region instead of spilling into a new one. Assisted-by: Claude Code https://claude.ai/code/session_016n3Q5CXgzQTFNC8qbPheGp
ca4dba1 to
7b3eb1e
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1294 +/- ##
==========================================
- Coverage 90.21% 90.20% -0.01%
==========================================
Files 36 36
Lines 16289 16318 +29
==========================================
+ Hits 14695 14720 +25
- Misses 1594 1598 +4
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Freeing a page could leave the allocator's per-order free-space index (the region tracker) stale, so the database file could grow instead of reusing freed space, and
compact()could stop before fully shrinking the file.Root cause
free_helper()marked the region tracker free only at the freed page's own order. But freeing a page can buddy-merge it into a block of a higher order. Once a region had been marked full at some order K (mark_full, when an allocation of order K failed on it), only a free at order >= K would clear that bit, becausemark_free(order)clears orders0..=order. A free at a lower order that merged up into a free order-K block left the bit set.With the bit stale,
find_free(K)skipped the region even though it held a free block, so allocations grew the file instead of reusing the space, andcompact()'sallocate_lowestprobe was steered to a higher page and hit itsnew >= oldearly break. The buddy allocator itself stayed exact, so no data was corrupted.Fix
BuddyAllocator::free()now returns the order of the resulting (possibly merged) block, andfree_helper()marks the region tracker free at that order. Returning it costs nothing — it is the terminal order of the existing merge recursion — and avoids re-scanning every order to recompute the region's highest free order on each free.Testing
free_merge_remarks_region_tracker, which fills a region, frees it so the pages merge back up, and asserts the next allocation reuses the region instead of spilling into a new one. Confirmed it fails before the fix and passes after.cargo fmt --check,cargo clippy --all-targets --all-features(deny warnings), andcargo test --all-featuresall pass.🤖 Generated with Claude Code
https://claude.ai/code/session_016n3Q5CXgzQTFNC8qbPheGp
Generated by Claude Code