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();