aio-libs/aiohttp · error · RuntimeError
WebSocket connection is closed.
Error message
WebSocket connection is closed.
What it means
Raised by WebSocketResponse.receive() when the connection is already closed and receive() has been called more than THRESHOLD_CONNLOST_ACCESS (5) times after closing. The first few calls after close return the WS_CLOSED_MESSAGE sentinel; after the threshold the library raises to stop a runaway loop from spinning on a dead connection.
Source
Thrown at aiohttp/web_ws.py:610
async def receive(
self: "WebSocketResponse[_DecodeText]", timeout: float | None = None
) -> WSMessageDecodeText | WSMessageNoDecodeText: ...
async def receive(
self, timeout: float | None = None
) -> WSMessageDecodeText | WSMessageNoDecodeText:
if self._reader is None:
raise RuntimeError("Call .prepare() first")
receive_timeout = timeout or self._receive_timeout
while True:
if self._waiting:
raise RuntimeError("Concurrent call to receive() is not allowed")
if self._closed:
self._conn_lost += 1
if self._conn_lost >= THRESHOLD_CONNLOST_ACCESS:
raise RuntimeError("WebSocket connection is closed.")
return WS_CLOSED_MESSAGE
elif self._closing:
return WS_CLOSING_MESSAGE
try:
self._waiting = True
try:
if receive_timeout:
# Entering the context manager and creating
# Timeout() object can take almost 50% of the
# run time in this loop so we avoid it if
# there is no read timeout.
async with async_timeout.timeout(receive_timeout):
msg = await self._reader.read()
else:
msg = await self._reader.read()
finally:
self._waiting = FalseView on GitHub (pinned to c0ef574e29)
Solutions
- Break the receive loop when msg.type is WSMsgType.CLOSE, CLOSING, or CLOSED.
- Check `ws.closed` before/after receive and stop looping when True.
- Prefer `async for msg in ws` which raises StopAsyncIteration on terminal message types and exits cleanly.
Example fix
# before
while True:
msg = await ws.receive()
handle(msg) # never breaks after peer closes
# after
async for msg in ws:
if msg.type in (WSMsgType.TEXT, WSMsgType.BINARY):
handle(msg)
# loop ends automatically on CLOSE/CLOSING/CLOSED Defensive patterns
Strategy: validation
Validate before calling
async def receive_until_closed(ws):
while not ws.closed:
msg = await ws.receive()
if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED):
break
yield msg Type guard
def is_open(ws) -> bool:
return not getattr(ws, "closed", True) Try / catch
try:
msg = await ws.receive()
except RuntimeError as e:
if "WebSocket connection is closed" in str(e):
# stop the loop; peer is gone
return None
raise Prevention
- Break receive loops on CLOSE/CLOSING/CLOSED message types.
- Prefer `async for msg in ws` which terminates cleanly.
- Check `ws.closed` before re-entering a loop.
When it happens
Trigger: A receive loop that keeps calling `await ws.receive()` after the peer closed and _closed is True. After 5 such calls (THRESHOLD_CONNLOST_ACCESS, defined at web_ws.py:63) the RuntimeError at line 610 fires instead of returning WS_CLOSED_MESSAGE.
Common situations: A `while True: msg = await ws.receive()` loop with no break on CLOSE/CLOSING/CLOSED message types; logic that ignores the closed sentinel and keeps polling; a bug where the exit condition is never met after disconnect.
Related errors
- Response has not been started
- Concurrent call to receive() is not allowed
- Received message {msg.type}:{msg.data!r} is not WSMsgType.TE
- Received message {msg.type}:{msg.data!r} is not WSMsgType.BI
- Compress wbits must between 9 and 15, zlib does not support
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/be9174c9b270c96f.json.
Report an issue: GitHub.