test: cover start race at rewrite barrier

This commit is contained in:
2026-09-04 16:53:35 +09:00
parent 60a5495ccd
commit fb13e53cb5
+57
View File
@@ -262,6 +262,8 @@ impl FeatureBackgroundTaskRegistryBuilder {
next_execution_id: AtomicU64::new(1),
session_generation: Arc::new(AtomicU64::new(1)),
lifecycle: AtomicU8::new(TASK_SCOPE_ACCEPTING),
#[cfg(test)]
start_race_probe: Mutex::new(None),
}),
}
}
@@ -274,6 +276,12 @@ struct RunningTask {
handle: JoinHandle<()>,
}
#[cfg(test)]
struct StartRaceProbe {
optimistic_check_passed: Arc<std::sync::Barrier>,
resume: Arc<std::sync::Barrier>,
}
struct RegistryInner {
registrations: BTreeMap<(FeatureId, String), Registration>,
running: Mutex<BTreeMap<u64, RunningTask>>,
@@ -281,6 +289,8 @@ struct RegistryInner {
next_execution_id: AtomicU64,
session_generation: Arc<AtomicU64>,
lifecycle: AtomicU8,
#[cfg(test)]
start_race_probe: Mutex<Option<StartRaceProbe>>,
}
impl Drop for RegistryInner {
@@ -395,6 +405,17 @@ impl FeatureBackgroundTaskRegistry {
"background task scope is stopping",
));
}
#[cfg(test)]
if let Some(probe) = self
.inner
.start_race_probe
.lock()
.expect("start race probe poisoned")
.take()
{
probe.optimistic_check_passed.wait();
probe.resume.wait();
}
let key = (feature_id.clone(), task_name.to_string());
let registration = self.inner.registrations.get(&key).ok_or_else(|| {
HookError::new(
@@ -889,6 +910,42 @@ mod tests {
}
}
#[tokio::test]
async fn starter_paused_after_optimistic_check_is_rejected_by_rewrite_barrier() {
let feature = FeatureId::builtin("rewrite-race");
let declaration = BackgroundTaskDeclaration::worker_managed("race", "race");
let mut builder = FeatureBackgroundTaskRegistryBuilder::default();
builder
.register(
feature.clone(),
BackgroundTaskSpec::single_flight(declaration, Duration::from_secs(1)),
WaitForCancellation,
)
.unwrap();
let registry = builder.build();
let optimistic_check_passed = Arc::new(std::sync::Barrier::new(2));
let resume = Arc::new(std::sync::Barrier::new(2));
*registry.inner.start_race_probe.lock().unwrap() = Some(StartRaceProbe {
optimistic_check_passed: Arc::clone(&optimistic_check_passed),
resume: Arc::clone(&resume),
});
let start_registry = registry.clone();
let starter = tokio::task::spawn_blocking(move || {
start_registry.start(&feature, "race", invocation())
});
optimistic_check_passed.wait();
let rewrite_guard = registry.begin_session_rewrite().await.unwrap();
resume.wait();
let error = starter.await.unwrap().unwrap_err();
assert_eq!(error.category, HookErrorCategory::ScopeDisposed);
assert!(registry.inner.running.lock().unwrap().is_empty());
drop(rewrite_guard);
assert!(registry.inner.running.lock().unwrap().is_empty());
registry.shutdown().await.unwrap();
}
#[tokio::test]
async fn cancellation_observed_when_cancel_races_with_wait_registration() {
for _ in 0..100 {