mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-06 03:59:14 +00:00
fix(scanner): refresh scope safety independently of idle backoff
Inspect maintenance on multi-disk startup and refresh changed or failed evidence even when explicit bitrot configuration disables idle backoff. Co-Authored-By: heihutu <heihutu@gmail.com> Co-Authored-By: zhi22915 <qiuzgang@gmail.com>
This commit is contained in:
@@ -1086,6 +1086,12 @@ impl ScannerMaintenanceFeatures {
|
||||
fn needs_regular_cycle(self) -> bool {
|
||||
self.lifecycle || self.replication || self.inspection_failed
|
||||
}
|
||||
|
||||
fn requires_full_scan(self, observed_generation: Option<u64>, current_generation: u64, wake: ScannerCycleWakeReason) -> bool {
|
||||
self.needs_regular_cycle()
|
||||
|| observed_generation != Some(current_generation)
|
||||
|| !matches!(wake, ScannerCycleWakeReason::DirtyUsage | ScannerCycleWakeReason::ClusterActivity)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -1283,18 +1289,18 @@ async fn configure_scanner_defaults(
|
||||
ctx: &CancellationToken,
|
||||
storeapi: &Arc<impl ScannerStorage>,
|
||||
) -> (ScannerMaintenanceFeatures, Option<u64>) {
|
||||
let (features, maintenance_generation) = detect_stable_scanner_maintenance_features(ctx, storeapi)
|
||||
.await
|
||||
.unwrap_or_else(|| {
|
||||
(
|
||||
ScannerMaintenanceFeatures {
|
||||
inspection_failed: true,
|
||||
..Default::default()
|
||||
},
|
||||
scanner_maintenance_generation(),
|
||||
)
|
||||
});
|
||||
if storeapi.setup_is_erasure_sd().await {
|
||||
let (features, maintenance_generation) = detect_stable_scanner_maintenance_features(ctx, storeapi)
|
||||
.await
|
||||
.unwrap_or_else(|| {
|
||||
(
|
||||
ScannerMaintenanceFeatures {
|
||||
inspection_failed: true,
|
||||
..Default::default()
|
||||
},
|
||||
scanner_maintenance_generation(),
|
||||
)
|
||||
});
|
||||
// Single-disk keeps the speed-preset-derived default cycle (60s at the
|
||||
// `default` preset) instead of a special shorter cycle: no measured
|
||||
// cold-start ILM latency basis for an override, and clean-idle backoff
|
||||
@@ -1319,7 +1325,7 @@ async fn configure_scanner_defaults(
|
||||
} else {
|
||||
set_scanner_default_speed(ScannerSpeed::Default);
|
||||
set_scanner_default_cycle_secs(None);
|
||||
(ScannerMaintenanceFeatures::default(), None)
|
||||
(features, Some(maintenance_generation))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2565,10 +2571,7 @@ where
|
||||
let mut superseded_backoff = ScannerRetryBackoff::default();
|
||||
let mut deferred_backoff = ScannerRetryBackoff::default();
|
||||
let initial_runtime_config = resolve_scanner_runtime_config();
|
||||
if clean_idle_topology_supported
|
||||
&& scanner_clean_idle_backoff_configured(&initial_runtime_config)
|
||||
&& maintenance_generation_seen.is_none()
|
||||
{
|
||||
if clean_idle_topology_supported && maintenance_generation_seen.is_none() {
|
||||
let Some((features, generation)) = detect_stable_scanner_maintenance_features(&ctx, &storeapi).await else {
|
||||
global_metrics().set_cycle(None).await;
|
||||
finish_scanner_leader_iteration(false, "stopped", String::new()).await;
|
||||
@@ -2871,7 +2874,7 @@ where
|
||||
#[cfg(test)]
|
||||
notify_scanner_runtime_observed_for_test(&storeapi, pause_backlog_observation);
|
||||
let runtime_config = resolve_scanner_runtime_config();
|
||||
if clean_idle_topology_supported && scanner_clean_idle_backoff_configured(&runtime_config) {
|
||||
if clean_idle_topology_supported {
|
||||
let current_generation = scanner_maintenance_generation();
|
||||
if maintenance_generation_seen != Some(current_generation) {
|
||||
scanner_activity_seen = None;
|
||||
@@ -3067,9 +3070,11 @@ where
|
||||
&mut cycle_revision,
|
||||
leader_epoch,
|
||||
cycle_budget.clone(),
|
||||
maintenance_features.needs_regular_cycle()
|
||||
|| maintenance_generation_seen != Some(scanner_maintenance_generation())
|
||||
|| !matches!(wake_reason, ScannerCycleWakeReason::DirtyUsage | ScannerCycleWakeReason::ClusterActivity),
|
||||
maintenance_features.requires_full_scan(
|
||||
maintenance_generation_seen,
|
||||
scanner_maintenance_generation(),
|
||||
wake_reason,
|
||||
),
|
||||
),
|
||||
guard.lock_lost_notified(),
|
||||
)
|
||||
@@ -3127,10 +3132,7 @@ where
|
||||
let maintenance_config_changed =
|
||||
maintenance_generation_seen.is_some_and(|generation| generation != current_maintenance_generation);
|
||||
let retry_failed_inspection = maintenance_inspection_retry.retry_due(maintenance_features, wake_reason, Instant::now());
|
||||
if clean_idle_topology_supported
|
||||
&& scanner_clean_idle_backoff_configured(&runtime_config)
|
||||
&& (maintenance_config_changed || retry_failed_inspection)
|
||||
{
|
||||
if clean_idle_topology_supported && (maintenance_config_changed || retry_failed_inspection) {
|
||||
let Some((features, generation)) = detect_stable_scanner_maintenance_features(&ctx, &storeapi).await else {
|
||||
break;
|
||||
};
|
||||
|
||||
@@ -8127,6 +8127,76 @@ fn clean_idle_backoff_policy_preserves_explicit_and_maintenance_cycles() {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn scoped_scan_explicit_bitrot_keeps_dirty_planning_without_idle_backoff() {
|
||||
temp_env::async_with_vars([(ENV_SCANNER_CYCLE, None), (ENV_SCANNER_BITROT_CYCLE_SECS, Some("3600"))], async {
|
||||
crate::runtime_config::refresh_scanner_runtime_config_for_tests();
|
||||
crate::scanner_io::clear_dirty_usage_buckets_for_tests();
|
||||
let (_temp_dir, store) = setup_scanner_cycle_store_with_pool_count(true, 2).await;
|
||||
let ctx = CancellationToken::new();
|
||||
let (features, generation) = configure_scanner_defaults(&ctx, &store).await;
|
||||
let config = resolve_scanner_runtime_config();
|
||||
assert_eq!(config.cycle_interval_source, ScannerRuntimeConfigSource::Default);
|
||||
assert_eq!(config.bitrot_cycle_source, ScannerRuntimeConfigSource::Env);
|
||||
assert!(!scanner_clean_idle_backoff_configured(&config));
|
||||
assert!(!features.needs_regular_cycle());
|
||||
assert_eq!(
|
||||
generation,
|
||||
Some(scanner_maintenance_generation()),
|
||||
"multi-disk startup must inspect maintenance independently"
|
||||
);
|
||||
let observed = ScannerCycleObservedGenerations::for_wait(&config, None, 7, 0, scanner_maintenance_generation());
|
||||
assert_eq!(observed.dirty_usage, Some(7), "explicit bitrot still permits ordinary dirty wakeups");
|
||||
for (wake, full) in [
|
||||
(ScannerCycleWakeReason::DirtyUsage, false),
|
||||
(ScannerCycleWakeReason::ClusterActivity, false),
|
||||
(ScannerCycleWakeReason::Timer, true),
|
||||
(ScannerCycleWakeReason::ClusterMaintenance, true),
|
||||
] {
|
||||
assert_eq!(
|
||||
features.requires_full_scan(generation, scanner_maintenance_generation(), wake),
|
||||
full,
|
||||
"{wake:?}"
|
||||
);
|
||||
}
|
||||
assert!(features.requires_full_scan(None, scanner_maintenance_generation(), ScannerCycleWakeReason::DirtyUsage));
|
||||
for unsafe_features in [
|
||||
ScannerMaintenanceFeatures {
|
||||
lifecycle: true,
|
||||
..Default::default()
|
||||
},
|
||||
ScannerMaintenanceFeatures {
|
||||
replication: true,
|
||||
..Default::default()
|
||||
},
|
||||
ScannerMaintenanceFeatures {
|
||||
inspection_failed: true,
|
||||
..Default::default()
|
||||
},
|
||||
] {
|
||||
assert!(unsafe_features.requires_full_scan(
|
||||
generation,
|
||||
scanner_maintenance_generation(),
|
||||
ScannerCycleWakeReason::DirtyUsage
|
||||
));
|
||||
}
|
||||
crate::scanner_io::record_scanner_maintenance_change("maintenance-proof-change");
|
||||
assert!(features.requires_full_scan(generation, scanner_maintenance_generation(), ScannerCycleWakeReason::DirtyUsage));
|
||||
let (refreshed, refreshed_generation) = detect_stable_scanner_maintenance_features(&ctx, &store)
|
||||
.await
|
||||
.expect("changed maintenance generation should be inspected");
|
||||
assert!(!refreshed.requires_full_scan(
|
||||
Some(refreshed_generation),
|
||||
scanner_maintenance_generation(),
|
||||
ScannerCycleWakeReason::DirtyUsage
|
||||
));
|
||||
crate::scanner_io::clear_dirty_usage_buckets_for_tests();
|
||||
})
|
||||
.await;
|
||||
crate::runtime_config::refresh_scanner_runtime_config_for_tests();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_idle_backoff_requires_activity_probes() {
|
||||
let default_config = ScannerRuntimeConfig::default();
|
||||
|
||||
Reference in New Issue
Block a user