fix(scanner): retain the enabled supervisor task handle

This commit is contained in:
overtrue
2026-10-26 12:15:33 +08:00
parent 94f344a7d4
commit 7b1ff0da45
2 changed files with 52 additions and 7 deletions
+13 -7
View File
@@ -1072,10 +1072,9 @@ async fn run_scanner_usage_recovery_intents_for_startup(
Ok(attempted)
}
/// Start normal scanning when enabled, or one bounded recovery attempt.
/// The disabled branch returns a finite task for the startup owner to join;
/// it never enables ordinary namespace scanning while it replays durable reset
/// intents and cleanup markers.
/// Return the normal scanner supervisor or one finite disabled recovery task.
/// The disabled branch never enables ordinary namespace scanning while it
/// replays durable reset intents and cleanup markers.
pub async fn init_scanner_with_recovery(
ctx: CancellationToken,
storeapi: Arc<ECStore>,
@@ -1096,8 +1095,7 @@ pub async fn init_scanner_with_recovery(
"Scanner recovery intent startup discovery failed"
);
}
init_data_scanner(ctx, storeapi).await;
return None;
return Some(init_data_scanner_task_with_storage(ctx, storeapi).await);
}
Some(tokio::spawn(async move {
if let Err(error) = run_scanner_usage_recovery_intents_for_startup(ctx.clone(), storeapi.clone()).await {
@@ -1126,6 +1124,14 @@ pub async fn init_scanner_with_recovery(
}
async fn init_data_scanner_with_storage<S>(ctx: CancellationToken, storeapi: Arc<S>)
where
S: ScannerStorage,
{
// Preserve the existing detached-start contract for legacy callers.
drop(init_data_scanner_task_with_storage(ctx, storeapi).await);
}
async fn init_data_scanner_task_with_storage<S>(ctx: CancellationToken, storeapi: Arc<S>) -> tokio::task::JoinHandle<()>
where
S: ScannerStorage,
{
@@ -1233,7 +1239,7 @@ where
break;
}
}
});
})
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@@ -239,6 +239,45 @@ async fn issue_8297_supervisor_recovers_paused_follower_without_restart() {
crate::runtime_config::refresh_scanner_runtime_config_for_tests();
}
#[tokio::test]
#[serial]
async fn enabled_scanner_startup_returns_owned_supervisor_handle() {
temp_env::async_with_vars([(ENV_SCANNER_CYCLE, Some("1")), (ENV_SCANNER_START_DELAY_SECS, Some("0"))], async {
crate::runtime_config::refresh_scanner_runtime_config_for_tests();
crate::scanner_io::clear_dirty_usage_buckets_for_tests();
global_metrics().set_cycle(None).await;
let (_dir, store) = setup_scanner_cycle_store().await;
assert!(!crate::scanner_runtime_initialized(), "test must own the scanner runtime");
let ctx = CancellationToken::new();
let _cancel_on_drop = ctx.clone().drop_guard();
let startup_probe = ScannerStartupObservedProbe::install();
let mut supervisor = init_scanner_with_recovery(ctx.clone(), store, true)
.await
.expect("enabled startup must return its actual supervisor handle");
startup_probe.wait().await;
assert!(crate::scanner_runtime_initialized(), "the observed supervisor owns its runtime guard");
assert!(!supervisor.is_finished(), "the observed supervisor remains in flight");
ctx.cancel();
assert!(
futures::poll!(&mut supervisor).is_pending(),
"cancellation cannot join the supervisor before its observed work is released"
);
startup_probe.resume();
drop(startup_probe);
tokio::time::timeout(Duration::from_secs(15), supervisor)
.await
.expect("cancelled supervisor must join within the existing test bound")
.expect("the exact enabled supervisor must exit successfully");
assert!(
!crate::scanner_runtime_initialized(),
"joining releases the actual supervisor runtime guard"
);
})
.await;
crate::runtime_config::refresh_scanner_runtime_config_for_tests();
}
async fn run_disabled_startup(ctx: CancellationToken, store: Arc<ECStore>) {
let initialized_before = crate::scanner_runtime_initialized();
let cleanup = init_scanner_with_recovery(ctx, store, false).await;