From b5b1cc25d7fc918469814d94b83e0b88d5a12d3b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Sat, 17 Oct 2026 03:55:58 +0000 Subject: [PATCH] test(daemon): plant a scheduler owner only once its task ended The concurrent re-enable test planted a finished scheduler owner, but its barrier was a signal sent from inside the task body. The native runtime marks the task finished only after that body returns, so a loaded worker left the owner reading as live. Both re-enables then notified it and the test saw (RunningNotified, RunningNotified), which is correct production behavior for a live owner. Await the planted task's outcome before inserting it, and print the outcome pair when the final assertion fails. Fixes #4165 --- .../src/daemon/tests/scheduler_config.rs | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/tracedecay/src/daemon/tests/scheduler_config.rs b/crates/tracedecay/src/daemon/tests/scheduler_config.rs index fab9edc6f8..b1c086db8f 100644 --- a/crates/tracedecay/src/daemon/tests/scheduler_config.rs +++ b/crates/tracedecay/src/daemon/tests/scheduler_config.rs @@ -370,15 +370,16 @@ async fn concurrent_reenable_creates_one_live_scheduler_owner() { let server = save_scheduled_automation(&engine, &handshake, true).await; let cg = server.cg().await; let key = ProjectServerKey::from_open_project(&cg, &handshake).expect("owner key"); - let (finished_tx, finished_rx) = tokio::sync::oneshot::channel(); - let finished = NativeTask::spawn(async move { - let _ = finished_tx.send(()); - }) - .expect("native task"); - tokio::time::timeout(tokio::time::Duration::from_secs(2), finished_rx) - .await - .expect("finished owner barrier timed out") - .expect("finished owner barrier sender dropped"); + let finished = NativeTask::spawn(async {}).expect("native task"); + // The planted owner must read as finished to both re-enables. A signal + // sent from inside its body precedes the task's end, and a task that has + // not ended is a live owner the re-enables rightly notify. + assert!(matches!( + tokio::time::timeout(tokio::time::Duration::from_secs(2), finished.wait()) + .await + .expect("finished owner barrier timed out"), + tracedecay_runtime_core::native_runtime::NativeTaskOutcome::Completed(()) + )); engine .automation_configured_override .store(true, std::sync::atomic::Ordering::Relaxed); @@ -456,16 +457,20 @@ async fn concurrent_reenable_creates_one_live_scheduler_owner() { drop(schedulers); engine.shutdown_all().await; - assert!(matches!( - (first, second), - ( - tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::Started, - tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::RunningNotified - ) | ( - tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::RunningNotified, - tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::Started - ) - )); + assert!( + matches!( + (first, second), + ( + tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::Started, + tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::RunningNotified + ) | ( + tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::RunningNotified, + tracedecay_contracts::retrieval::AutomationSchedulerReconcileOutcome::Started + ) + ), + "one re-enable must start the owner and the other must notify it: {:?}", + (first, second) + ); } #[cfg(unix)]