mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-26 05:56:50 +00:00
fix(scanner): fence system metadata publication (#6444)
* feat(scanner): fence usage publication during data movement * fix(scanner): detect movement refresh state changes * fix(scanner): fence publication during data movement * fix(scanner): close movement epoch publication races * fix(scanner): fence movement-sensitive publication paths * fix(scanner): fence cache and heal recovery paths * fix(scanner): carry publication epoch through scan cycle * fix(scanner): recheck remote cache epoch after save * fix(scanner): recheck local cache epoch before publish * fix(scanner): fence data usage writers and baseline * fix(scanner): expose decommission activity to publication fence * fix(scanner): release publication gate before reads * fix(scanner): complete publication fence integration * fix(scanner): avoid empty usage baseline publication * chore(scanner): gate test-only helpers * fix: use decommission canceler in reload test --------- Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -52,11 +52,18 @@ use crate::services::tier::tier::TierConfigMgr;
|
||||
use rustfs_lock::{GlobalLockManager, get_global_lock_manager};
|
||||
use s3s::region::Region;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::sync::{
|
||||
Arc, OnceLock,
|
||||
atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering},
|
||||
};
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use uuid::Uuid;
|
||||
|
||||
const SCANNER_PUBLICATION_STATE_UNKNOWN: u8 = 0;
|
||||
const SCANNER_PUBLICATION_STATE_ALLOWED: u8 = 1;
|
||||
const SCANNER_PUBLICATION_STATE_BLOCKED: u8 = 2;
|
||||
|
||||
/// Runtime state owned by a single `ECStore` instance.
|
||||
///
|
||||
/// This is intentionally minimal in the first migration slice; subsequent
|
||||
@@ -160,10 +167,22 @@ pub struct InstanceContext {
|
||||
/// workers (scanner/heal/tier/lifecycle) without touching another instance.
|
||||
/// Replaces the process-global cancel-token static.
|
||||
background_cancel_token: OnceLock<CancellationToken>,
|
||||
/// Serializes decommission data-movement operations with cancellation and
|
||||
/// a subsequent restart. Readers are held across one object side effect;
|
||||
/// the transition path takes the writer after cancelling the routine.
|
||||
decommission_operation_gate: Arc<RwLock<()>>,
|
||||
/// Serializes data-movement transitions with scanner publication commits.
|
||||
/// Readers are held across one publication commit; movement transitions
|
||||
/// take the writer at their durable state commit boundary.
|
||||
data_movement_operation_gate: Arc<RwLock<()>>,
|
||||
/// Monotonic admission epoch paired with the operation gate. A
|
||||
/// publication admitted before a movement transition must never be
|
||||
/// mistaken for one admitted after the transition.
|
||||
data_movement_operation_epoch: AtomicU64,
|
||||
/// Once the admission epoch reaches its reserved terminal value, no new
|
||||
/// publication may be admitted. Keeping this state separate from the
|
||||
/// saturating counter prevents an unchanged `u64::MAX` value from being
|
||||
/// mistaken for a fresh epoch after overflow.
|
||||
data_movement_operation_epoch_exhausted: AtomicBool,
|
||||
/// Last storage-owned movement snapshot observed under the operation
|
||||
/// gate. SetDisks cache writers fail closed until ECStore refreshes it.
|
||||
scanner_publication_state: AtomicU8,
|
||||
/// Resolves object-encryption material at the application boundary.
|
||||
object_encryption_resolver: OnceLock<Arc<dyn ObjectEncryptionResolver>>,
|
||||
tier_delete_journal_recovery_stores: std::sync::Mutex<HashSet<Uuid>>,
|
||||
@@ -204,7 +223,10 @@ impl InstanceContext {
|
||||
local_disk_set_drives: Arc::new(RwLock::new(Vec::new())),
|
||||
bucket_metadata_sys: std::sync::Mutex::new(None),
|
||||
background_cancel_token: OnceLock::new(),
|
||||
decommission_operation_gate: Arc::new(RwLock::new(())),
|
||||
data_movement_operation_gate: Arc::new(RwLock::new(())),
|
||||
data_movement_operation_epoch: AtomicU64::new(0),
|
||||
data_movement_operation_epoch_exhausted: AtomicBool::new(false),
|
||||
scanner_publication_state: AtomicU8::new(SCANNER_PUBLICATION_STATE_UNKNOWN),
|
||||
object_encryption_resolver: OnceLock::new(),
|
||||
tier_delete_journal_recovery_stores: std::sync::Mutex::new(HashSet::new()),
|
||||
transition_transaction_recovery_stores: std::sync::Mutex::new(HashSet::new()),
|
||||
@@ -223,8 +245,54 @@ impl InstanceContext {
|
||||
self.lock_manager.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn decommission_operation_gate(&self) -> Arc<RwLock<()>> {
|
||||
Arc::clone(&self.decommission_operation_gate)
|
||||
pub(crate) fn data_movement_operation_gate(&self) -> Arc<RwLock<()>> {
|
||||
Arc::clone(&self.data_movement_operation_gate)
|
||||
}
|
||||
|
||||
pub(crate) fn data_movement_operation_epoch(&self) -> u64 {
|
||||
self.data_movement_operation_epoch.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn data_movement_operation_epoch_exhausted(&self) -> bool {
|
||||
self.data_movement_operation_epoch_exhausted.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn scanner_publication_state_allowed(&self) -> bool {
|
||||
!self.data_movement_operation_epoch_exhausted()
|
||||
&& self.scanner_publication_state.load(Ordering::Acquire) == SCANNER_PUBLICATION_STATE_ALLOWED
|
||||
}
|
||||
|
||||
pub(crate) fn set_scanner_publication_state(&self, blocked: bool) {
|
||||
self.scanner_publication_state.store(
|
||||
if blocked {
|
||||
SCANNER_PUBLICATION_STATE_BLOCKED
|
||||
} else {
|
||||
SCANNER_PUBLICATION_STATE_ALLOWED
|
||||
},
|
||||
Ordering::Release,
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn advance_data_movement_operation_epoch(&self) -> u64 {
|
||||
self.scanner_publication_state
|
||||
.store(SCANNER_PUBLICATION_STATE_UNKNOWN, Ordering::Release);
|
||||
let _ = self
|
||||
.data_movement_operation_epoch
|
||||
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |epoch| Some(epoch.saturating_add(1)));
|
||||
let result = self.data_movement_operation_epoch.load(Ordering::Acquire);
|
||||
if result == u64::MAX {
|
||||
self.data_movement_operation_epoch_exhausted.store(true, Ordering::Release);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_data_movement_operation_epoch_for_test(&self, epoch: u64) {
|
||||
self.data_movement_operation_epoch.store(epoch, Ordering::Release);
|
||||
self.data_movement_operation_epoch_exhausted
|
||||
.store(epoch == u64::MAX, Ordering::Release);
|
||||
self.scanner_publication_state
|
||||
.store(SCANNER_PUBLICATION_STATE_UNKNOWN, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Install the application-owned object-encryption resolver once.
|
||||
|
||||
Reference in New Issue
Block a user