From 8d339da706df8868d7d094f36f34b4fbc4a686e3 Mon Sep 17 00:00:00 2001 From: houseme Date: Tue, 8 Sep 2026 14:11:27 +0800 Subject: [PATCH] heal: reject cancelled object repair receipts Do not record positive storage repair receipts once an object heal task has been cancelled, even if the receipt still matches the requested owner and object identity. Co-Authored-By: heihutu Co-Authored-By: zhi22915 --- crates/heal/src/heal/task.rs | 2 +- crates/heal/src/heal/task/tests.rs | 37 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/crates/heal/src/heal/task.rs b/crates/heal/src/heal/task.rs index 7984ec3df..2c171e76e 100644 --- a/crates/heal/src/heal/task.rs +++ b/crates/heal/src/heal/task.rs @@ -609,7 +609,7 @@ impl HealTask { expected: HealObjectIdentity, receipt: Option, ) -> bool { - if self.options.dry_run { + if self.options.dry_run || self.cancel_token.is_cancelled() { return false; } let Some(receipt) = receipt else { diff --git a/crates/heal/src/heal/task/tests.rs b/crates/heal/src/heal/task/tests.rs index f2a6ec36a..9c6a06a9d 100644 --- a/crates/heal/src/heal/task/tests.rs +++ b/crates/heal/src/heal/task/tests.rs @@ -1328,6 +1328,43 @@ async fn object_heal_records_matching_positive_storage_receipt() { assert_eq!(object.disposition, HealObjectDisposition::Repaired); } +#[tokio::test] +async fn cancelled_object_heal_rejects_matching_positive_storage_receipt() { + let incarnation = Uuid::new_v4(); + let storage = Arc::new(MockStorage::default()); + let task = HealTask::from_request( + HealRequest::object("bucket-a".to_string(), "object-a".to_string(), Some("version-a".to_string())), + storage, + ); + task.cancel().await.expect("task cancellation should succeed"); + + let expected = HealObjectIdentity { + kind: HealObjectKind::Object, + bucket: "bucket-a".to_string(), + object: "object-a".to_string(), + version_id: Some("version-a".to_string()), + bucket_incarnation_id: Some(incarnation), + pool_index: None, + set_index: None, + }; + let accepted = task + .record_verified_storage_receipt( + expected, + Some(object_receipt( + "object-a", + Some("version-a"), + HealObjectDisposition::Repaired, + incarnation, + )), + ) + .await; + + let outcome = task.get_outcome().await; + assert!(!accepted); + assert_eq!(outcome.counters.healed, 0); + assert!(outcome.objects.is_empty()); +} + #[tokio::test] async fn object_heal_latches_expected_incarnation_before_repair() { let original_incarnation = Uuid::new_v4();