fix(room): widen WS handler exception scope to JSONDecodeError + RuntimeError
A single malformed JSON message (or a "WebSocket is not connected" race
on disconnect) was killing the per-client handler with an uncaught
exception in the ASGI app. The surrounding try/except only caught
WebSocketDisconnect, so the server would log a stack trace and the
client would silently drop.
Wrap receive_json() to catch JSONDecodeError, send a structured
{"type":"error","code":"bad_message"} ack, and continue. Widen the
outer except to (WebSocketDisconnect, RuntimeError) so disconnect
races on send/receive after close exit the handler cleanly instead
of bubbling up the ASGI stack.
Both student_ws and instructor_ws hardened in parallel. 31/31 pytest
suite still passes; this fixes the recurring fuzz-scenario warn and
the cycle-187-style cascade observed in the stress loop.
This commit is contained in:
22
app/room.py
22
app/room.py
@@ -84,7 +84,14 @@ class RoomManager:
|
|||||||
try:
|
try:
|
||||||
await self.send_student_snapshot(websocket, sid, identity)
|
await self.send_student_snapshot(websocket, sid, identity)
|
||||||
while True:
|
while True:
|
||||||
data = await websocket.receive_json()
|
try:
|
||||||
|
data = await websocket.receive_json()
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
try:
|
||||||
|
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Invalid JSON"})
|
||||||
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
|
break
|
||||||
|
continue
|
||||||
msg_type = data.get("type")
|
msg_type = data.get("type")
|
||||||
if msg_type == "ping":
|
if msg_type == "ping":
|
||||||
await websocket.send_json({"type": "pong"})
|
await websocket.send_json({"type": "pong"})
|
||||||
@@ -93,7 +100,7 @@ class RoomManager:
|
|||||||
await websocket.send_json(ack)
|
await websocket.send_json(ack)
|
||||||
else:
|
else:
|
||||||
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Unknown message type"})
|
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Unknown message type"})
|
||||||
except WebSocketDisconnect:
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.student_clients[sid].pop(websocket, None)
|
self.student_clients[sid].pop(websocket, None)
|
||||||
@@ -104,7 +111,14 @@ class RoomManager:
|
|||||||
try:
|
try:
|
||||||
await self.send_instructor_snapshot(websocket, sid)
|
await self.send_instructor_snapshot(websocket, sid)
|
||||||
while True:
|
while True:
|
||||||
data = await websocket.receive_json()
|
try:
|
||||||
|
data = await websocket.receive_json()
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
try:
|
||||||
|
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Invalid JSON"})
|
||||||
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
|
break
|
||||||
|
continue
|
||||||
msg_type = data.get("type")
|
msg_type = data.get("type")
|
||||||
if msg_type == "ping":
|
if msg_type == "ping":
|
||||||
await websocket.send_json({"type": "pong"})
|
await websocket.send_json({"type": "pong"})
|
||||||
@@ -118,7 +132,7 @@ class RoomManager:
|
|||||||
await self.end_session(sid)
|
await self.end_session(sid)
|
||||||
else:
|
else:
|
||||||
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Unknown message type"})
|
await websocket.send_json({"type": "error", "code": "bad_message", "message": "Unknown message type"})
|
||||||
except WebSocketDisconnect:
|
except (WebSocketDisconnect, RuntimeError):
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
self.instructor_clients[sid].discard(websocket)
|
self.instructor_clients[sid].discard(websocket)
|
||||||
|
|||||||
Reference in New Issue
Block a user