fix(scanner): back off clean idle scans across erasure clusters (#4984)

* fix(scanner): back off clean single-disk cycles

* fix(scanner): extend idle backoff across erasure clusters

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
This commit is contained in:
Henry Guo
2026-07-18 10:49:45 +08:00
committed by GitHub
parent 5ec124bf23
commit 889a45ad4d
23 changed files with 3542 additions and 241 deletions
+46
View File
@@ -2434,6 +2434,18 @@ impl Metrics {
self.current_scan_cycle_work_active.store(false, Ordering::Relaxed);
}
pub fn current_scan_cycle_has_unresolved_heal_work(&self) -> bool {
if !self.current_scan_cycle_work_active.load(Ordering::Relaxed) {
return false;
}
let source_work = self.scanner_source_work_since(&self.current_scan_cycle_source_work_start_values());
[ScannerWorkSource::Heal, ScannerWorkSource::Bitrot]
.into_iter()
.filter_map(|source| source_work.get(source.index()))
.any(|work| work.queued > 0 || work.skipped > 0 || work.failed > 0 || work.missed > 0)
}
fn scan_cycle_work_snapshot(&self) -> ScanCycleWorkSnapshot {
ScanCycleWorkSnapshot {
objects_scanned: self.lifetime(Metric::ScanObject),
@@ -3361,6 +3373,40 @@ mod tests {
metrics.finish_scan_cycle_work(start);
}
#[test]
fn unresolved_heal_work_only_reflects_the_active_cycle() {
let metrics = Metrics::new();
assert!(!metrics.current_scan_cycle_has_unresolved_heal_work());
let start = metrics.start_scan_cycle_work();
metrics.record_scanner_source_missed(ScannerWorkSource::Heal, 1);
assert!(metrics.current_scan_cycle_has_unresolved_heal_work());
metrics.finish_scan_cycle_work(start);
assert!(!metrics.current_scan_cycle_has_unresolved_heal_work());
let start = metrics.start_scan_cycle_work();
metrics.record_scanner_source_queued(ScannerWorkSource::Heal, 1);
assert!(metrics.current_scan_cycle_has_unresolved_heal_work());
metrics.finish_scan_cycle_work(start);
let start = metrics.start_scan_cycle_work();
metrics.record_scanner_source_work(
ScannerWorkSource::Bitrot,
ScannerSourceWorkUpdate {
skipped: 1,
..Default::default()
},
);
assert!(metrics.current_scan_cycle_has_unresolved_heal_work());
metrics.finish_scan_cycle_work(start);
let start = metrics.start_scan_cycle_work();
metrics.record_scanner_source_failed(ScannerWorkSource::Bitrot, 1);
assert!(metrics.current_scan_cycle_has_unresolved_heal_work());
metrics.finish_scan_cycle_work(start);
}
#[tokio::test]
async fn report_marks_transition_failures_as_blocked_lifecycle_control() {
let metrics = Metrics::new();