fix(orchestrator-core): F-Q-006 + F-Q-010 InboxRegistry expect on poison

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) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 20:52:05 +02:00
parent 547c9f9950
commit 6b5da50a38

View File

@@ -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<InboxResult>) {
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;
};