From 6b5da50a38226852641f446dd3aabfaf56cddd90 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 7 Jun 2026 20:52:05 +0200 Subject: [PATCH] fix(orchestrator-core): F-Q-006 + F-Q-010 InboxRegistry expect on poison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InboxRegistry::register returned (Uuid, Receiver) even when the inner Mutex was poisoned — `if let Ok(mut g) = self.inner.lock()` swallowed the error, the sender was never inserted, the later deliver(id, …) saw no entry and returned Abandoned, and the caller's `await rx` blocked until the orchestrator's outer timeout. Sister registries (RouteTable, AppDomainTable) already .expect() panic on poison. Make InboxRegistry::{register, cancel, deliver} loud in the same way. A poisoned Mutex means a prior panic inside a critical section — the process is unrecoverable; silently swallowing it just defers the crash and corrupts the inbox protocol on the way. AUDIT.md anchors: F-Q-006 (register), F-Q-010 (consistency policy). Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/orchestrator-core/src/inbox.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/orchestrator-core/src/inbox.rs b/crates/orchestrator-core/src/inbox.rs index 15a7ef8..805902a 100644 --- a/crates/orchestrator-core/src/inbox.rs +++ b/crates/orchestrator-core/src/inbox.rs @@ -42,13 +42,21 @@ impl InboxRegistry { /// Allocate a new inbox id and register the sender side. The /// caller awaits the returned `Receiver`; the dispatcher delivers /// the outcome via `deliver(id, …)`. + /// + /// F-Q-006: panic on poison — a poisoned mutex means a prior panic + /// inside the lock-holding code path, so the process is unrecoverable. + /// Silently swallowing it (the old behaviour) leaked a dead id: the + /// sender never got inserted, the later `deliver(id, …)` saw no + /// entry and returned Abandoned, and the caller's `await rx` blocked + /// until the orchestrator's outer timeout. Sister registries + /// (RouteTable, AppDomainTable) already `.expect()` here — settle + /// on the same loud policy. #[must_use] pub fn register(&self) -> (Uuid, oneshot::Receiver) { let id = Uuid::new_v4(); let (tx, rx) = oneshot::channel(); - if let Ok(mut g) = self.inner.lock() { - g.insert(id, tx); - } + let mut g = self.inner.lock().expect("inbox registry poisoned"); + g.insert(id, tx); (id, rx) } @@ -56,10 +64,8 @@ impl InboxRegistry { /// Drops the sender so any future `deliver` returns `Abandoned`. /// Returns `true` if the receiver was still registered. pub fn cancel(&self, id: Uuid) -> bool { - self.inner - .lock() - .map(|mut g| g.remove(&id).is_some()) - .unwrap_or(false) + let mut g = self.inner.lock().expect("inbox registry poisoned"); + g.remove(&id).is_some() } } @@ -72,9 +78,9 @@ impl Default for InboxRegistry { #[async_trait] impl InboxResolver for InboxRegistry { async fn deliver(&self, inbox_id: Uuid, result: InboxResult) -> InboxDeliveryOutcome { - let Ok(mut g) = self.inner.lock() else { - return InboxDeliveryOutcome::Abandoned; - }; + // F-Q-006 + F-Q-010: panic on poison, matching the registry's + // register/cancel policy and the sister registries. + let mut g = self.inner.lock().expect("inbox registry poisoned"); let Some(tx) = g.remove(&inbox_id) else { return InboxDeliveryOutcome::Abandoned; };