worker: persist child completion before wakeup

This commit is contained in:
2026-09-18 19:12:55 +09:00
parent 19b04a8d8f
commit 72f0b82ae5
+30 -5
View File
@@ -272,17 +272,18 @@ impl ParentNotificationTarget {
};
match self {
Self::Controller { sender, fallback } => {
// Persist through the Worker-owned pending authority before the
// child completion path returns. The Controller delivery is only
// a wake-up hint; replay is idempotent by notification_request_id.
fallback(method.clone());
let Some(parent_method_tx) = sender.upgrade() else {
fallback(method);
return;
};
let fallback = fallback.clone();
tokio::spawn(async move {
if let Err(error) = parent_method_tx.send(method).await {
if parent_method_tx.send(method).await.is_err() {
tracing::warn!(
"failed to notify parent Controller; using durable pending authority"
"failed to wake parent Controller after durable notification acceptance"
);
fallback(error.0);
}
});
}
@@ -1201,6 +1202,30 @@ enabled = false
assert!(*captured.lock().unwrap());
}
#[tokio::test]
async fn parent_controller_notification_is_durable_before_wakeup_delivery() {
let (parent_method_tx, mut parent_method_rx) = mpsc::channel(1);
let durable = Arc::new(AtomicBool::new(false));
let durable_for_fallback = Arc::clone(&durable);
let target = ParentNotificationTarget::with_controller_fallback(
parent_method_tx.downgrade(),
ParentNotificationTarget::Durable(Arc::new(move |_| {
durable_for_fallback.store(true, Ordering::Release);
})),
);
target.notify("child-session".into(), "completed".into());
let wakeup = parent_method_rx.recv().await.unwrap();
assert!(durable.load(Ordering::Acquire));
assert!(matches!(
wakeup,
Method::NotifyTracked {
source: protocol::AuthenticatedInputSource::SubWorker { session_id },
..
} if session_id == "child-session"
));
}
#[test]
fn durable_parent_notification_target_preserves_child_source() {
let captured = Arc::new(std::sync::Mutex::new(None));