Fix WS deadlock: never block on perm_rx in the chat handler

Since story 884 the Matrix permission listener (and the sled uplink,
when configured) hold services.perm_rx for the process lifetime. The
WS chat handler's blocking `lock().await` on that same mutex therefore
parked the entire WS connection loop forever on StartChat: chat_fut was
never polled, RPC frames on the socket were never answered, and
everything queued behind the dispatcher's serial session lock —
wedging /mcp and /rpc while /health stayed green.

Use try_lock instead: if another task already owns permission routing,
run the chat without the local permission-forwarding select arm (a
pending future keeps the select shape unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fHdm92yjvguPi2LiXfLB9
This commit is contained in:
Timmy
2026-07-15 17:17:24 +01:00
co-authored by Claude Fable 5
parent 01b24ff2ae
commit ba0a38d403
+23 -2
View File
@@ -147,13 +147,34 @@ pub async fn ws_handler(ws: WebSocket, ctx: Data<&Arc<AppContext>>) -> impl poem
);
tokio::pin!(chat_fut);
let mut perm_rx = ctx.services.perm_rx.lock().await;
// Take perm_rx for local permission forwarding — but never
// block on it. Since story 884 the Matrix permission
// listener (and the sled uplink, when configured) hold this
// lock for the process lifetime, so a blocking
// `lock().await` here parks the entire WS connection loop
// forever: chat_fut is never polled, RPC frames on this
// socket are never answered, and everything queues behind
// the dispatcher's serial session lock. If another task
// already owns permission routing, proceed without the
// local forwarding arm — requests are handled there.
let mut perm_rx = ctx.services.perm_rx.try_lock().ok();
if perm_rx.is_none() {
crate::slog!(
"[ws] perm_rx held by another listener; \
skipping local permission forwarding for this chat"
);
}
let chat_result = loop {
tokio::select! {
result = &mut chat_fut => break result,
Some(perm_fwd) = perm_rx.recv() => {
Some(perm_fwd) = async {
match perm_rx.as_mut() {
Some(rx) => rx.recv().await,
None => std::future::pending().await,
}
} => {
let _ = tx.send(ws::permission_request_response(
&perm_fwd.request_id,
&perm_fwd.tool_name,