{"record":{"id":"be9174c9b270c96f","repo":"aio-libs/aiohttp","slug":"websocket-connection-is-closed","errorCode":null,"errorMessage":"WebSocket connection is closed.","messagePattern":"WebSocket connection is closed\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":610,"sourceCode":"    async def receive(\n        self: \"WebSocketResponse[_DecodeText]\", timeout: float | None = None\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText: ...\n\n    async def receive(\n        self, timeout: float | None = None\n    ) -> WSMessageDecodeText | WSMessageNoDecodeText:\n        if self._reader is None:\n            raise RuntimeError(\"Call .prepare() first\")\n\n        receive_timeout = timeout or self._receive_timeout\n        while True:\n            if self._waiting:\n                raise RuntimeError(\"Concurrent call to receive() is not allowed\")\n\n            if self._closed:\n                self._conn_lost += 1\n                if self._conn_lost >= THRESHOLD_CONNLOST_ACCESS:\n                    raise RuntimeError(\"WebSocket connection is closed.\")\n                return WS_CLOSED_MESSAGE\n            elif self._closing:\n                return WS_CLOSING_MESSAGE\n\n            try:\n                self._waiting = True\n                try:\n                    if receive_timeout:\n                        # Entering the context manager and creating\n                        # Timeout() object can take almost 50% of the\n                        # run time in this loop so we avoid it if\n                        # there is no read timeout.\n                        async with async_timeout.timeout(receive_timeout):\n                            msg = await self._reader.read()\n                    else:\n                        msg = await self._reader.read()\n                finally:\n                    self._waiting = False","sourceCodeStart":592,"sourceCodeEnd":628,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_ws.py#L592-L628","documentation":"Raised inside receive() once self._conn_lost reaches THRESHOLD_CONNLOST_ACCESS (=5). After the connection is marked closed, each receive() call increments _conn_lost and returns WS_CLOSED_MESSAGE up to 4 times; on the 5th call aiohttp raises to stop a caller from busy-looping receive() on a dead socket.","triggerScenarios":"Application keeps calling `await ws.receive()` (or iterating) after the peer has disconnected and the socket is closed; a loop that ignores WS_CLOSED_MESSAGE / WS_CLOSING_MESSAGE and re-enters receive().","commonSituations":"A `while True: msg = await ws.receive()` loop with no break on msg.type in (CLOSE, CLOSING, CLOSED, ERROR); a retry loop that swallows the closed sentinel; reconnect logic that reuses the dead WebSocketResponse.","solutions":["Break your receive loop when msg.type is WSMsgType.CLOSED, CLOSING, CLOSE, or ERROR.","Check `ws.closed` before re-entering receive().","After close, create a new WebSocketResponse for a new connection rather than reusing the closed one.","Handle WS_CLOSED_MESSAGE explicitly instead of treating it as data."],"exampleFix":"// before\nwhile True:\n    msg = await ws.receive()  # raises after 5th call post-close\n    process(msg)\n\n// after\nwhile True:\n    msg = await ws.receive()\n    if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED, WSMsgType.ERROR):\n        break\n    process(msg)","handlingStrategy":"validation","validationCode":"while not ws.closed:\n    msg = await ws.receive()\n    if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED, WSMsgType.ERROR):\n        break\n    handle(msg)","typeGuard":"def ws_is_dead(ws: web.WebSocketResponse) -> bool:\n    return ws.closed  # property: self._closed","tryCatchPattern":"try:\n    msg = await ws.receive()\nexcept RuntimeError as e:\n    if 'connection is closed' in str(e):\n        break  # stop looping on the dead socket\n    raise","preventionTips":["Break receive loops on CLOSE/CLOSING/CLOSED/ERROR message types.","Check `ws.closed` before re-entering receive().","Create a fresh WebSocketResponse for a new connection; never reuse a closed one."],"tags":["websocket","lifecycle","receive","server","connection-lost"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}