mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-01 01:38:18 +00:00
fix(ecstore): fence decommission routine generations
This commit is contained in:
@@ -1247,6 +1247,7 @@ fn is_decommission_cancel_requested(cancel_signal: bool, pool: Option<&PoolStatu
|
|||||||
.is_some_and(|info| info.canceled)
|
.is_some_and(|info| info.canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
fn should_skip_canceled_decommission_routine(cancel_signal: bool, pool: Option<&PoolStatus>) -> bool {
|
fn should_skip_canceled_decommission_routine(cancel_signal: bool, pool: Option<&PoolStatus>) -> bool {
|
||||||
cancel_signal
|
cancel_signal
|
||||||
&& pool
|
&& pool
|
||||||
@@ -2854,13 +2855,28 @@ impl ECStore {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub async fn decommission_cancel(&self, idx: usize) -> Result<()> {
|
pub async fn decommission_cancel(&self, idx: usize) -> Result<()> {
|
||||||
|
self.decommission_cancel_with_generation(idx, None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn decommission_cancel_with_generation(&self, idx: usize, generation: Option<OffsetDateTime>) -> Result<()> {
|
||||||
ensure_decommission_terminal_operation_supported(self.single_pool(), "cancel decommission")?;
|
ensure_decommission_terminal_operation_supported(self.single_pool(), "cancel decommission")?;
|
||||||
|
|
||||||
let _start_guard = self.start_gate.lock().await;
|
let _start_guard = self.start_gate.lock().await;
|
||||||
let canceled_worker = self.cancel_decommission_routine_and_wait(idx).await;
|
let canceled_worker = if generation.is_some() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
self.cancel_decommission_routine_and_wait(idx).await
|
||||||
|
};
|
||||||
|
|
||||||
let (should_save_pool_meta, should_reload_pool_meta, already_canceled, previous_pool_meta) = {
|
let (should_save_pool_meta, should_reload_pool_meta, already_canceled, previous_pool_meta) = {
|
||||||
let mut lock = self.pool_meta.write().await;
|
let mut lock = self.pool_meta.write().await;
|
||||||
|
if let Some(generation) = generation {
|
||||||
|
match ensure_decommission_generation(&lock, idx, generation) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(Error::OperationCanceled) => return Ok(()),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut already_canceled = false;
|
let mut already_canceled = false;
|
||||||
let (pool_present, decommission_present, terminal) = if let Some(pool) = lock.pools.get(idx) {
|
let (pool_present, decommission_present, terminal) = if let Some(pool) = lock.pools.get(idx) {
|
||||||
if let Some(info) = pool.decommission.as_ref() {
|
if let Some(info) = pool.decommission.as_ref() {
|
||||||
@@ -2888,6 +2904,12 @@ impl ECStore {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if generation.is_some() && should_save_pool_meta {
|
||||||
|
let mut cancelers = self.decommission_cancelers.write().await;
|
||||||
|
take_and_cancel_decommission_canceler(cancelers.as_mut_slice(), idx);
|
||||||
|
}
|
||||||
|
let canceled_worker = canceled_worker || (generation.is_some() && should_save_pool_meta);
|
||||||
|
|
||||||
if !canceled_worker && !already_canceled {
|
if !canceled_worker && !already_canceled {
|
||||||
warn!(
|
warn!(
|
||||||
event = EVENT_DECOMMISSION_STATE,
|
event = EVENT_DECOMMISSION_STATE,
|
||||||
@@ -2960,21 +2982,48 @@ impl ECStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn promote_queued_decommission(&self, idx: usize) -> Result<()> {
|
async fn promote_queued_decommission(&self, idx: usize) -> Result<OffsetDateTime> {
|
||||||
let promoted = {
|
// Serialize promotion and generation capture with clear/restart transitions.
|
||||||
|
let (promoted, generation, save_error) = {
|
||||||
|
let _start_guard = self.start_gate.lock().await;
|
||||||
let mut pool_meta = self.pool_meta.write().await;
|
let mut pool_meta = self.pool_meta.write().await;
|
||||||
pool_meta.promote_queued_decommission(idx)
|
let promoted = pool_meta.promote_queued_decommission(idx);
|
||||||
|
drop(pool_meta);
|
||||||
|
|
||||||
|
let save_error = if promoted {
|
||||||
|
self.save_current_pool_meta().await.err()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let generation = self.active_decommission_generation(idx).await?;
|
||||||
|
(promoted, generation, save_error)
|
||||||
};
|
};
|
||||||
|
|
||||||
if promoted {
|
if let Some(err) = save_error {
|
||||||
self.save_current_pool_meta().await?;
|
resolve_decommission_terminal_mark_after_error_result(
|
||||||
if let Some(notification_sys) = runtime_sources::notification_sys() {
|
self.decommission_failed_with_generation(idx, Some(generation)).await,
|
||||||
let stage = format!("promote_queued_decommission for pool {idx}");
|
idx,
|
||||||
resolve_decommission_pool_meta_reload_result(notification_sys.reload_pool_meta().await, stage.as_str())?;
|
&err,
|
||||||
|
)?;
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if promoted && let Some(notification_sys) = runtime_sources::notification_sys() {
|
||||||
|
let stage = format!("promote_queued_decommission for pool {idx}");
|
||||||
|
if let Err(err) =
|
||||||
|
resolve_decommission_pool_meta_reload_result(notification_sys.reload_pool_meta().await, stage.as_str())
|
||||||
|
{
|
||||||
|
resolve_decommission_terminal_mark_after_error_result(
|
||||||
|
self.decommission_failed_with_generation(idx, Some(generation)).await,
|
||||||
|
idx,
|
||||||
|
&err,
|
||||||
|
)?;
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(generation)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn record_decommission_terminal_reload_failure(&self, idx: usize, stage: &str, err: Error) -> Result<()> {
|
async fn record_decommission_terminal_reload_failure(&self, idx: usize, stage: &str, err: Error) -> Result<()> {
|
||||||
@@ -4158,36 +4207,26 @@ impl ECStore {
|
|||||||
idx: usize,
|
idx: usize,
|
||||||
entry_budget: Arc<Semaphore>,
|
entry_budget: Arc<Semaphore>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Err(err) = self.promote_queued_decommission(idx).await {
|
let generation = match self.promote_queued_decommission(idx).await {
|
||||||
resolve_decommission_terminal_mark_after_error_result(self.decommission_failed(idx).await, idx, &err)?;
|
Ok(generation) => generation,
|
||||||
return Err(err);
|
Err(Error::OperationCanceled) => return Ok(()),
|
||||||
}
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
defer!(|| async {
|
||||||
|
self.clear_decommission_canceler_for_generation(idx, generation).await;
|
||||||
|
});
|
||||||
|
|
||||||
if rx.is_cancelled() {
|
if rx.is_cancelled() {
|
||||||
let already_canceled = {
|
if let Err(err) = self.decommission_cancel_with_generation(idx, Some(generation)).await {
|
||||||
let pool_meta = self.pool_meta.read().await;
|
resolve_decommission_terminal_mark_after_error_result(
|
||||||
should_skip_canceled_decommission_routine(true, pool_meta.pools.get(idx))
|
self.decommission_failed_with_generation(idx, Some(generation)).await,
|
||||||
};
|
idx,
|
||||||
if already_canceled {
|
&err,
|
||||||
warn!(
|
)?;
|
||||||
event = EVENT_DECOMMISSION_STATE,
|
|
||||||
component = LOG_COMPONENT_ECSTORE,
|
|
||||||
subsystem = LOG_SUBSYSTEM_POOLS,
|
|
||||||
pool_index = idx,
|
|
||||||
state = "canceled_preserved",
|
|
||||||
"Decommission routine skipped because pool is already canceled"
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if let Err(err) = self.decommission_cancel(idx).await {
|
|
||||||
resolve_decommission_terminal_mark_after_error_result(self.decommission_failed(idx).await, idx, &err)?;
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let generation = self.active_decommission_generation(idx).await?;
|
|
||||||
defer!(|| async {
|
|
||||||
self.clear_decommission_canceler_for_generation(idx, generation).await;
|
|
||||||
});
|
|
||||||
let result = self.decommission_in_background(rx.clone(), idx, entry_budget).await;
|
let result = self.decommission_in_background(rx.clone(), idx, entry_budget).await;
|
||||||
|
|
||||||
let (final_state, canceled, cmd_line) = {
|
let (final_state, canceled, cmd_line) = {
|
||||||
@@ -7878,6 +7917,16 @@ mod pools_tests {
|
|||||||
.expect("decommission metadata should exist")
|
.expect("decommission metadata should exist")
|
||||||
.queued = true;
|
.queued = true;
|
||||||
assert!(ensure_decommission_generation(&meta, 0, generation).is_err());
|
assert!(ensure_decommission_generation(&meta, 0, generation).is_err());
|
||||||
|
|
||||||
|
let replacement_generation = generation + Duration::seconds(2);
|
||||||
|
let info = meta.pools[0]
|
||||||
|
.decommission
|
||||||
|
.as_mut()
|
||||||
|
.expect("decommission metadata should exist");
|
||||||
|
info.queued = false;
|
||||||
|
info.start_time = Some(replacement_generation);
|
||||||
|
assert!(ensure_decommission_generation(&meta, 0, generation).is_err());
|
||||||
|
assert!(ensure_decommission_generation(&meta, 0, replacement_generation).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user