You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
replay: fix race between FlushEnd and refreshMetrics
527ea2b added a FlushEnd handler that closes compactionMu.ch to wake
refreshMetrics when a flush completes. Unlike CompactionEnd, the
handler does not increment the completed counter because that counter
only tracks compactions. This creates a gap: nextCompactionCompletes
has no way to detect that a flush notification was already delivered,
so it can create a new channel that nobody will ever close.
The race occurs in refreshMetrics between r.d.Metrics() and
nextCompactionCompletes(). If a flush completes in this window:
1. r.d.Metrics() acquires d.mu, observes flushing=true
(Flush.NumInProgress=1), releases d.mu.
2. The flush goroutine acquires d.mu, completes, fires FlushEnd
(under d.mu). The replay handler closes compactionMu.ch and
nils it.
3. nextCompactionCompletes sees ch==nil, creates a new channel.
With no counter increment to detect the flush, it returns
alreadyOccurred=false.
4. compactionsAppearQuiesced uses the stale metrics from step 1
(NumInProgress=1) and returns false.
5. The loop re-enters the first select with a channel nobody will
close and stepsApplied==nil (blocks forever). Permanent hang.
Fix this with three changes:
1. Track both flushes and compactions in the started/completed
counters. Rename compactionMu to compactionOrFlushMu and add a
FlushBegin handler that increments started. The FlushEnd handler
now also increments completed, matching CompactionEnd. This allows
nextCompactionOrFlushCompletes to detect flush completions through
the counter, eliminating the race.
2. Switch compactionsAppearQuiesced to use only the started/completed
counter (started == completed) instead of checking
DB.Metrics().NumInProgress. There is a scheduling window between
AddInProgressLocked (which increments NumInProgress under d.mu)
and CompactionBegin (which fires in a separate goroutine that must
re-acquire d.mu). During this window NumInProgress > 0 but
started == completed. Using NumInProgress would block quiescence
detection during cascading compactions. The counter does not have
this window, and the 1-second quiescence confirmation handles any
false positives from compactions that are scheduled but have not
yet fired CompactionBegin.
3. Fix a pre-existing tight loop in refreshMetrics: when
nextCompactionOrFlushCompletes detects a completion via the
counter (alreadyOccurred=true), the old code skipped the first
select and immediately re-acquired d.mu to collect metrics, even
though the quiescence check would be skipped anyway. Under heavy
compaction load this tight loop contends with event handlers for
d.mu. Fix this by catching up the counter without collecting
metrics when alreadyOccurred is true, then falling through to
collect metrics once caught up.
Fixes#5820.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0 commit comments