From 0f31cc41ccf8b4884b131f6b64b028b37990e84e Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sun, 28 Jun 2026 15:34:53 +0200 Subject: [PATCH] Handle abrupt WebSocket disconnect (e.g. Safari tab close) Catch ConnectionClosed in User.send() and handler_recv() so that an unclean client disconnect no longer produces "Task exception was never retrieved" / "connection handler failed" log noise. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01SH2D4LRuCnhLSzoYerAfJX --- ws/server/ws_server_multi_user.py | 20 ++++++++++++-------- ws/user.py | 6 +++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/ws/server/ws_server_multi_user.py b/ws/server/ws_server_multi_user.py index 2348995..272772d 100644 --- a/ws/server/ws_server_multi_user.py +++ b/ws/server/ws_server_multi_user.py @@ -1,4 +1,5 @@ import asyncio +import websockets.exceptions from ws.server.ws_server import WsServer from ws.user import User, UserSet, update from ws.connection import IConnection @@ -31,14 +32,17 @@ class WsServerMultiUser(WsServer): await self.notify_users() async def handler_recv(self, websocket, path): - while True: - data = await websocket.recv() - usr = self.USERS.get(websocket) - processed = await usr.process(data) - if processed: - await usr.send(self.global_state) - else: - await self.listener.on_recv(data) + try: + while True: + data = await websocket.recv() + usr = self.USERS.get(websocket) + processed = await usr.process(data) + if processed: + await usr.send(self.global_state) + else: + await self.listener.on_recv(data) + except websockets.exceptions.ConnectionClosed: + pass async def handler_send(self, websocket, path): while True: diff --git a/ws/user.py b/ws/user.py index 046a892..e7f8e67 100644 --- a/ws/user.py +++ b/ws/user.py @@ -1,5 +1,6 @@ import dpath.util as dp import json +import websockets.exceptions def update(d, u, only_existing=True): @@ -26,7 +27,10 @@ class User: msg = dp.search(data, path) result.update(msg) if len(result) != 0: - await self.socket.send(json.dumps(result)) + try: + await self.socket.send(json.dumps(result)) + except websockets.exceptions.ConnectionClosed: + pass async def process(self, data): handled = True