From cee84561e7d3f27186a52e8473475cc533540bfc Mon Sep 17 00:00:00 2001 From: cxymds Date: Tue, 1 Sep 2026 16:55:57 +0800 Subject: [PATCH] fix(heal): preserve resumable retry signals (#6995) --- crates/heal/src/heal/task/heal_erasure_set.rs | 1 + crates/heal/src/heal/task/tests.rs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/crates/heal/src/heal/task/heal_erasure_set.rs b/crates/heal/src/heal/task/heal_erasure_set.rs index 5d5e25d42..df532293a 100644 --- a/crates/heal/src/heal/task/heal_erasure_set.rs +++ b/crates/heal/src/heal/task/heal_erasure_set.rs @@ -492,6 +492,7 @@ impl HealTask { } Err(Error::TaskCancelled) => Err(Error::TaskCancelled), Err(Error::TaskTimeout) => Err(Error::TaskTimeout), + Err(e) if e.is_recoverable_heal() => Err(e), Err(e) => { error!( target: "rustfs::heal::task", diff --git a/crates/heal/src/heal/task/tests.rs b/crates/heal/src/heal/task/tests.rs index 1878ca7ea..c1ed4d6a7 100644 --- a/crates/heal/src/heal/task/tests.rs +++ b/crates/heal/src/heal/task/tests.rs @@ -2428,6 +2428,37 @@ async fn erasure_set_format_slowdown_is_propagated() { assert!(matches!(error, Error::Storage(EcstoreError::SlowDown))); } +#[tokio::test] +async fn erasure_set_retry_signal_remains_typed_across_task_boundary() { + let temp = TempDir::new().expect("temporary directory should be created"); + let disk = make_resume_disk(&temp).await; + let storage = Arc::new(MockStorage { + heal_object_outcome: Mutex::new(Some(MockHealObjectOutcome::RetryableSlowDown)), + resume_disk: Mutex::new(Some(disk)), + ..Default::default() + }); + let request = HealRequest::new( + HealType::ErasureSet { + buckets: vec!["bucket-a".to_string()], + set_disk_id: "pool_0_set_0".to_string(), + }, + HealOptions::default(), + HealPriority::Normal, + ); + let task = HealTask::from_request(request, storage); + + let error = task + .execute() + .await + .expect_err("an incomplete resumable pass must remain retryable"); + + assert!( + matches!(&error, Error::TransientSkip { message } if message.contains("retry scheduled")), + "the resumable retry signal must keep its typed identity: {error}" + ); + assert!(error.is_recoverable_heal(), "the scheduler must accept the preserved retry signal"); +} + #[tokio::test] async fn erasure_set_bucket_prepass_failure_stops_before_object_heal() { let temp = TempDir::new().expect("temporary directory should be created");