fix(heal): cancel cluster tasks from root stop (#5978)

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
This commit is contained in:
Henry Guo
2026-08-12 20:46:35 +08:00
committed by GitHub
parent 4c5e73b2f2
commit 7a4a3d27c6
3 changed files with 106 additions and 25 deletions
+47 -2
View File
@@ -1778,9 +1778,22 @@ mod tests {
}
#[tokio::test]
async fn test_process_cancel_request_treats_unknown_path_as_stopped() {
async fn test_process_cancel_request_cancels_cluster_task_for_legacy_root_path() {
let heal_manager = create_test_heal_manager();
let processor = HealChannelProcessor::new(heal_manager);
let cluster_request = HealRequest::new(HealType::Cluster, HealOptions::default(), HealPriority::High);
let cluster_task_id = cluster_request.id.clone();
let bucket_request = HealRequest::bucket("bucket".to_string());
let bucket_task_id = bucket_request.id.clone();
heal_manager
.submit_heal_request(cluster_request)
.await
.expect("cluster request should be accepted");
heal_manager
.submit_heal_request(bucket_request)
.await
.expect("bucket request should be accepted");
let processor = HealChannelProcessor::new(heal_manager.clone());
let (tx, rx) = oneshot::channel();
processor
@@ -1796,6 +1809,38 @@ mod tests {
assert_eq!(response.request_id, ".");
assert_eq!(response.data.as_deref(), Some("stopped".as_bytes()));
assert!(response.error.is_none());
assert!(matches!(
heal_manager.get_task_status(&cluster_task_id).await,
Err(crate::Error::TaskNotFound { .. })
));
assert_eq!(
heal_manager
.get_task_status(&bucket_task_id)
.await
.expect("bucket request should not match the root path"),
HealTaskStatus::Pending
);
}
#[tokio::test]
async fn test_process_cancel_request_treats_unknown_path_as_stopped() {
let heal_manager = create_test_heal_manager();
let processor = HealChannelProcessor::new(heal_manager);
let (tx, rx) = oneshot::channel();
processor
.process_cancel_request("missing".to_string(), String::new(), tx)
.await
.expect("cancel should process");
let response = rx
.await
.expect("oneshot should resolve")
.expect("cancel response should be returned");
assert!(response.success);
assert_eq!(response.request_id, "missing");
assert_eq!(response.data.as_deref(), Some("stopped".as_bytes()));
assert!(response.error.is_none());
}
#[tokio::test]
+13 -1
View File
@@ -52,6 +52,7 @@ const EVENT_HEAL_MAINLINE_THROTTLE: &str = "heal_mainline_throttle";
const EVENT_HEAL_SCHEDULER_STATE: &str = "heal_scheduler_state";
const EVENT_HEAL_QUEUE_STATE: &str = "heal_queue_state";
const EVENT_HEAL_UNCLEAN_SHUTDOWN: &str = "heal_unclean_shutdown";
const LEGACY_ROOT_HEAL_PATH: &str = ".";
const MAX_RECOVERABLE_HEAL_RETRIES: u32 = 3;
const MAX_RECOVERABLE_HEAL_RETRY_DELAY: Duration = Duration::from_secs(30);
@@ -601,7 +602,7 @@ impl RetryingHeal {
fn heal_type_matches_path(heal_type: &HealType, heal_path: &str) -> bool {
let heal_path = heal_path.trim_matches('/');
if heal_path.is_empty() {
if heal_path.is_empty() || heal_path == LEGACY_ROOT_HEAL_PATH {
return matches!(heal_type, HealType::Cluster);
}
@@ -5110,6 +5111,17 @@ mod tests {
assert!(manager.retrying_heals.lock().await.get(&bucket_request_id).is_some());
}
#[test]
fn test_heal_type_matches_path_accepts_legacy_root() {
assert!(heal_type_matches_path(&HealType::Cluster, LEGACY_ROOT_HEAL_PATH));
assert!(!heal_type_matches_path(
&HealType::Bucket {
bucket: "bucket".to_string(),
},
LEGACY_ROOT_HEAL_PATH,
));
}
#[tokio::test]
async fn test_retrying_duplicate_token_can_query_and_cancel_original_retry() {
let storage: Arc<dyn HealStorageAPI> = Arc::new(MockStorage);