fix(scanner): honor explicit cycle cadence (#6313)

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
Henry Guo
2026-08-20 23:35:03 +08:00
committed by GitHub
parent b2e573c48b
commit 105b6fbfde
4 changed files with 58 additions and 8 deletions
+7 -8
View File
@@ -1804,14 +1804,13 @@ async fn run_data_scanner_with_maintenance_state(
wait_plan.delay, wait_plan.delay,
activity_poll_interval, activity_poll_interval,
&mut scanner_activity_seen, &mut scanner_activity_seen,
ScannerCycleObservedGenerations { ScannerCycleObservedGenerations::for_wait(
// A non-converged cycle holds further activity notifications &runtime_config,
// until its bounded retry timer to avoid an unbroken scan loop. convergence_retry_interval,
dirty_usage: convergence_retry_interval.is_none().then_some(dirty_usage_generation_seen), dirty_usage_generation_seen,
runtime_config: runtime_config_generation_seen, runtime_config_generation_seen,
maintenance: maintenance_generation_before_wait, maintenance_generation_before_wait,
defer_cluster_activity: convergence_retry_interval.is_some(), ),
},
|| guard.is_lock_lost(), || guard.is_lock_lost(),
|| probe_scanner_activity(storeapi.as_ref(), distributed), || probe_scanner_activity(storeapi.as_ref(), distributed),
) )
+21
View File
@@ -229,6 +229,27 @@ pub(super) struct ScannerCycleObservedGenerations {
pub(super) defer_cluster_activity: bool, pub(super) defer_cluster_activity: bool,
} }
impl ScannerCycleObservedGenerations {
pub(super) fn for_wait(
runtime_config: &ScannerRuntimeConfig,
convergence_retry_interval: Option<Duration>,
dirty_usage_generation_seen: u64,
runtime_config_generation: u64,
maintenance_generation: u64,
) -> Self {
Self {
// An explicit cycle override is a duty-cycle policy; dirty usage
// wakes stay on the default adaptive path so the interval holds.
dirty_usage: (convergence_retry_interval.is_none()
&& runtime_config.cycle_interval_source == ScannerRuntimeConfigSource::Default)
.then_some(dirty_usage_generation_seen),
runtime_config: runtime_config_generation,
maintenance: maintenance_generation,
defer_cluster_activity: convergence_retry_interval.is_some(),
}
}
}
pub(super) const LOCAL_SCANNER_ACTIVITY_NODE: &str = "<local>"; pub(super) const LOCAL_SCANNER_ACTIVITY_NODE: &str = "<local>";
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
+25
View File
@@ -3107,6 +3107,31 @@ fn clean_idle_backoff_requires_activity_probes() {
assert!(!scanner_activity_probe_required(true, false, lifecycle, &default_config)); assert!(!scanner_activity_probe_required(true, false, lifecycle, &default_config));
} }
#[test]
fn dirty_usage_wakes_are_disabled_for_explicit_cycle_policy() {
let default_config = ScannerRuntimeConfig::default();
let default_observed = ScannerCycleObservedGenerations::for_wait(&default_config, None, 7, 11, 13);
assert_eq!(default_observed.dirty_usage, Some(7));
assert_eq!(default_observed.runtime_config, 11);
assert_eq!(default_observed.maintenance, 13);
assert!(!default_observed.defer_cluster_activity);
let retry_observed = ScannerCycleObservedGenerations::for_wait(&default_config, Some(Duration::from_secs(11)), 7, 11, 13);
assert_eq!(retry_observed.dirty_usage, None);
assert!(retry_observed.defer_cluster_activity);
for source in [ScannerRuntimeConfigSource::Env, ScannerRuntimeConfigSource::Config] {
let explicit_cycle = ScannerRuntimeConfig {
cycle_interval_source: source,
..default_config.clone()
};
let explicit_observed = ScannerCycleObservedGenerations::for_wait(&explicit_cycle, None, 7, 11, 13);
assert_eq!(explicit_observed.dirty_usage, None);
assert!(!explicit_observed.defer_cluster_activity);
}
}
#[test] #[test]
#[serial] #[serial]
fn clean_idle_cap_preserves_default_bitrot_coverage_window() { fn clean_idle_cap_preserves_default_bitrot_coverage_window() {
@@ -70,6 +70,11 @@ sleep multiplier, maximum wait, and cycle interval. Use `scanner.delay`,
`scanner.max_wait`, and `scanner.cycle` when the preset is close but one axis `scanner.max_wait`, and `scanner.cycle` when the preset is close but one axis
needs a precise override. needs a precise override.
An explicit `scanner.cycle` or `RUSTFS_SCANNER_CYCLE` is a minimum inter-cycle
cadence: dirty-usage notifications do not bypass that configured interval.
The default adaptive policy continues to use dirty-usage notifications to wake
the scanner between timer-driven cycles.
## Single-disk clean-idle scheduling ## Single-disk clean-idle scheduling
An erasure single-disk deployment using the built-in cycle and bitrot defaults An erasure single-disk deployment using the built-in cycle and bitrot defaults