Fix compaction abort rescheduling before queue pick#14800
Conversation
AbortAllCompactions can race with an already-scheduled automatic compaction before the background worker calls PickCompactionFromQueue(). MaybeScheduleFlushOrCompaction() has already consumed one unscheduled_compactions_ credit when it scheduled the worker, but the CF is still present in compaction_queue_ with queued_for_compaction=true. If the worker returns kCompactionAborted before popping the CF, ResumeAllCompactions() can see unscheduled_compactions_ == 0 and fail to schedule the still-queued work, leaving compaction permanently stalled until DB restart. Restore the unscheduled compaction credit in the non-prepicked abort path, matching the existing BG-work-stopped handling for the same scheduled-before-pick state. Prepicked/manual compactions are not adjusted because they do not represent an unpopped automatic compaction_queue_ entry whose scheduling credit was consumed. Add AbortScheduledAutomaticCompactionBeforePick to deterministically reproduce the lost-credit race with a sync point at BackgroundCallCompaction:0. The test verifies that ResumeAllCompactions() schedules the still-queued automatic compaction by checking COMPACT_WRITE_BYTES advances and L0 file count drops. Add a bug-fix release note under unreleased_history/bug_fixes for the next release. Test Plan: without the implementation fix, AbortScheduledAutomaticCompactionBeforePick fails because COMPACT_WRITE_BYTES remains 0 after ResumeAllCompactions(). Verified final tree with make format-auto, make -j128 db_compaction_abort_test, timeout 60s ./db_compaction_abort_test --gtest_filter=DBCompactionAbortTest.AbortScheduledAutomaticCompactionBeforePick, timeout 60s ./db_compaction_abort_test, COERCE_CONTEXT_SWITCH=1 make -j128 db_compaction_abort_test && timeout 60s ./db_compaction_abort_test --gtest_filter=DBCompactionAbortTest.AbortScheduledAutomaticCompactionBeforePick --gtest_repeat=5, and make check-sources. After adding the release note, reran make check-sources.
✅ clang-tidy: No findings on changed linesCompleted in 108.4s. |
🟡 Codex Code ReviewAuto-triggered after CI passed — reviewing commit 81e4eb2 ❌ Codex review failed before producing findings. ℹ️ About this responseGenerated by Codex CLI. Limitations:
Commands:
|
✅ Claude Code ReviewAuto-triggered after CI passed — reviewing commit 81e4eb2 SummaryThis PR correctly fixes a real race condition where High-severity findings (0): No high-severity findings. Full review (click to expand)Findings🔴 HIGHNone. 🟡 MEDIUMM1. Inconsistent
|
| Context | Affected? | Verified? |
|---|---|---|
| WritePreparedTxnDB | No | Same scheduling path |
| FIFO/Universal compaction | Yes, works correctly | Style-agnostic scheduling |
| Multiple CFs | Yes, works correctly | Per-worker credit accounting |
| Multiple BG threads | Yes, works correctly | Each worker restores own credit |
| Nested abort/resume | Yes, works correctly | Counter semantics preserved |
| Manual compaction | No (guarded by !is_prepicked) |
Verified via caller audit |
| Forwarded-to-bottom compaction | No (guarded by !is_prepicked) |
Verified: original pick consumes credit, forwarded worker is prepicked |
| ReadOnly DB | No | No compaction scheduling |
Key invariant verified: MaybeScheduleFlushOrCompaction() at line 3995 (in BackgroundCallCompaction cleanup) cannot consume the restored credit because compaction_aborted_ is still > 0 at that point. AbortAllCompactions() waits for bg_compaction_scheduled_ == 0 (line 3188), which happens AFTER line 3986's decrement, so ResumeAllCompactions() cannot be called until after the cleanup completes.
Positive Observations
- The fix precisely mirrors the existing BG-work-stopped pattern (line 4080), demonstrating good codebase awareness.
- The
!is_prepickedguard is more correct than the existing BG-work-stopped path, showing attention to the credit accounting model. - The test uses
SyncPointAbortHelperconsistently with other tests in the file. - The comment on the new code clearly explains the invariant being maintained.
- The sync point
"BackgroundCallCompaction:0"precisely targets the window between worker scheduling and queue pick, making the test deterministic.
ℹ️ About this response
Generated by Claude Code.
Review methodology: claude_md/code_review.md
Limitations:
- Claude may miss context from files not in the diff
- Large PRs may be truncated
- Always apply human judgment to AI suggestions
Commands:
/claude-review [context]— Request a code review/claude-query <question>— Ask about the PR or codebase
81e4eb2 to
578e1d9
Compare
|
@xingbowang has imported this pull request. If you are a Meta employee, you can view this in D106684155. |
Summary
AbortAllCompactions can race with an already-scheduled automatic compaction before the background worker calls PickCompactionFromQueue(). MaybeScheduleFlushOrCompaction() has already consumed one unscheduled_compactions_ credit when it scheduled the worker, but the CF is still present in compaction_queue_ with queued_for_compaction=true. If the worker returns kCompactionAborted before popping the CF, ResumeAllCompactions() can see unscheduled_compactions_ == 0 and fail to schedule the still-queued work, leaving compaction permanently stalled until DB restart.
Restore the unscheduled compaction credit in the non-prepicked abort path, matching the existing BG-work-stopped handling for the same scheduled-before-pick state. Prepicked/manual compactions are not adjusted because they do not represent an unpopped automatic compaction_queue_ entry whose scheduling credit was consumed.
Add AbortScheduledAutomaticCompactionBeforePick to deterministically reproduce the lost-credit race with a sync point at BackgroundCallCompaction:0. The test verifies that ResumeAllCompactions() schedules the still-queued automatic compaction by checking COMPACT_WRITE_BYTES advances and L0 file count drops.
Test Plan
DBCompactionAbortTest.AbortScheduledAutomaticCompactionBeforePickfails becauseCOMPACT_WRITE_BYTESremains unchanged afterResumeAllCompactions().