fix(scanner): fence movement generation publication (#6461)

* feat(scanner): add movement generation fencing

* fix(scanner): prioritize unverified cycle deferral

* feat(ecstore): add scanner publication lease fence

* feat(rpc): add scanner publication lease protocol

* feat(scanner): hold remote leases through usage publish

* test(scanner): cover publication lease fencing

* fix(scanner): fence remote leases across restart and delay

* feat(rpc): fence scanner publication rename writes

* fix(scanner): fence observed cleanup deletes

* fix(proto): qualify lease release test types

* fix(scanner): pin movement notifications

* fix(scanner): clean publication imports

* fix(ecstore): satisfy scanner fence clippy

* refactor(scanner): group wait and publication options

* fix(scanner): satisfy final lint and facade guards

* fix(rpc): resolve facade export conflicts

* fix(ci): remove unused decommission and healing facades

* fix(ci): cfg-gate test-only usage overlay import

* fix(scanner): wake on remote scanner restart
This commit is contained in:
cxymds
2026-08-24 14:17:35 +08:00
committed by GitHub
parent 1c5c28842a
commit eec0e0e056
30 changed files with 2926 additions and 230 deletions
+28 -5
View File
@@ -239,6 +239,13 @@ fn classify_nsscanner_cycle(
dirty_usage_status: DirtyUsageSnapshotStatus,
activity_status: ScannerCycleActivityStatus,
) -> ScannerCycleStatus {
// The post-scan activity proof is required regardless of why the scan was
// incomplete. Returning Incomplete first would apply the long ordinary
// retry/backoff path to an unverifiable publication and could acknowledge
// a cycle without a movement-generation proof.
if activity_status == ScannerCycleActivityStatus::Unverified {
return ScannerCycleStatus::Deferred(ScannerCycleDeferReason::ActivityBaselineUnavailable);
}
if budget_elapsed
|| cancelled
|| !matches!(bucket_scan_status, ScannerBucketScanStatus::Complete)
@@ -252,7 +259,6 @@ fn classify_nsscanner_cycle(
match (activity_status, dirty_usage_status) {
(ScannerCycleActivityStatus::Unchanged, DirtyUsageSnapshotStatus::Current) => ScannerCycleStatus::Complete,
(ScannerCycleActivityStatus::Unverified, _) => ScannerCycleStatus::Incomplete,
_ => ScannerCycleStatus::Superseded,
}
}
@@ -307,10 +313,16 @@ async fn scanner_cycle_activity_status(
store: &ECStore,
distributed: bool,
before: &crate::scanner::ScannerActivitySnapshot,
) -> ScannerCycleActivityStatus {
) -> (ScannerCycleActivityStatus, Vec<(String, String, u64)>) {
match crate::scanner::probe_scanner_activity(store, distributed).await {
Ok(after) if after == *before => ScannerCycleActivityStatus::Unchanged,
Ok(_) => ScannerCycleActivityStatus::Changed,
Ok(after) => {
let status = if after == *before {
ScannerCycleActivityStatus::Unchanged
} else {
ScannerCycleActivityStatus::Changed
};
(status, crate::scanner::scanner_activity_publication_lease_targets(&after))
}
Err(err) => {
warn!(
target: "rustfs::scanner::io",
@@ -321,7 +333,7 @@ async fn scanner_cycle_activity_status(
error = %err,
"Scanner cycle activity verification failed"
);
ScannerCycleActivityStatus::Unverified
(ScannerCycleActivityStatus::Unverified, Vec::new())
}
}
}
@@ -599,6 +611,7 @@ pub(crate) struct ScannerCycleResult {
publication_epoch: Option<u64>,
dirty_usage_clear: Option<DirtyUsageBuckets>,
remote_dirty_usage_acknowledgements: Vec<crate::scanner::ScannerDirtyUsageAcknowledgement>,
remote_publication_lease_targets: Vec<(String, String, u64)>,
failed_dirty_usage: bool,
pending_maintenance_work: bool,
required_cycle_floor: Option<u64>,
@@ -611,6 +624,7 @@ impl ScannerCycleResult {
publication_epoch: None,
dirty_usage_clear,
remote_dirty_usage_acknowledgements: Vec::new(),
remote_publication_lease_targets: Vec::new(),
failed_dirty_usage: false,
pending_maintenance_work: false,
required_cycle_floor: None,
@@ -649,6 +663,15 @@ impl ScannerCycleResult {
self
}
pub(crate) fn with_remote_publication_lease_targets(mut self, targets: Vec<(String, String, u64)>) -> Self {
self.remote_publication_lease_targets = targets;
self
}
pub(crate) fn remote_publication_lease_targets(&self) -> &[(String, String, u64)] {
&self.remote_publication_lease_targets
}
pub(crate) fn acknowledge_durable_usage(self) -> Vec<crate::scanner::ScannerDirtyUsageAcknowledgement> {
if let Some(snapshot) = self.dirty_usage_clear {
clear_dirty_usage_buckets(&snapshot);