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 by tracking 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 behavior. This allows nextCompactionOrFlushCompletes to
detect flush completions through the counter, eliminating the race.
Also change compactionsAppearQuiesced to check each condition
independently (metrics and counter) rather than conjoining them.
The conjunction was an optimization to handle stale metrics, but it
can miss a flush that starts between r.d.Metrics() and the quiescence
check. The disjunction is more defensively correct and the cost is at
most one extra loop iteration.
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, starving
them and delaying quiescence. 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