fix(monitor): quiet send-after-close race on WS disconnect
When a monitor subscriber disconnects mid-frame (the client portal closes its stream on every tab switch via the Page Visibility guard), the loop could pull a queued payload during the 1s wait and then send_json into an already-closing socket -> "Unexpected ASGI message 'websocket.send' after ... websocket.close", logged as a WARNING on every disconnect. Re-check gone.done() after the queue wait and break before sending; treat the residual send-after-close as expected (debug, not warning). No behavior change — the connection was already closing as intended; this just stops the log spam. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -284,11 +284,21 @@ async def monitor_stream(websocket: WebSocket, unit_id: str):
|
|||||||
payload = await asyncio.wait_for(queue.get(), timeout=1.0)
|
payload = await asyncio.wait_for(queue.get(), timeout=1.0)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
continue # re-check gone.done()
|
continue # re-check gone.done()
|
||||||
|
if gone.done():
|
||||||
|
break # client disconnected while we waited — don't send into a closing socket
|
||||||
await websocket.send_json(payload)
|
await websocket.send_json(payload)
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
logger.info(f"Monitor subscriber disconnected for {unit_id}")
|
logger.info(f"Monitor subscriber disconnected for {unit_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Monitor stream error for {unit_id}: {e}")
|
# A frame that races the close (client vanished mid-send) surfaces as
|
||||||
|
# "Unexpected ASGI message 'websocket.send' after ... websocket.close".
|
||||||
|
# That's expected on disconnect (the portal closes the socket on every tab
|
||||||
|
# switch), not an error — log it quietly.
|
||||||
|
msg = str(e)
|
||||||
|
if "after sending" in msg or "websocket.close" in msg or "response already completed" in msg:
|
||||||
|
logger.debug(f"Monitor stream for {unit_id} closed mid-send (client gone)")
|
||||||
|
else:
|
||||||
|
logger.warning(f"Monitor stream error for {unit_id}: {e}")
|
||||||
finally:
|
finally:
|
||||||
gone.cancel()
|
gone.cancel()
|
||||||
await monitor.unsubscribe(queue)
|
await monitor.unsubscribe(queue)
|
||||||
|
|||||||
Reference in New Issue
Block a user