aio-libs/aiohttp · error · RuntimeError
Concurrent call to receive() is not allowed
Error message
Concurrent call to receive() is not allowed
What it means
Raised by WebSocketResponse.receive() when a second concurrent receive is attempted. The _waiting flag is set True while a read() on the internal queue is in flight and cleared in a finally block. WebSocketResponse is not safe for concurrent receive(); only one coroutine may await a message at a time.
Source
Thrown at aiohttp/web_ws.py:605
async def receive(
self: "WebSocketResponse[Literal[False]]", timeout: float | None = None
) -> WSMessageNoDecodeText: ...
@overload
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):View on GitHub (pinned to c0ef574e29)
Solutions
- Serialize all reads through a single consumer: have one loop call receive() and dispatch messages to other tasks via queues.
- Ensure only one task iterates the WebSocketResponse; remove any second receive()/async-for on the same object.
- If you need fan-out, feed received messages into an asyncio.Queue and have multiple workers pull from the queue.
Example fix
# before
# two tasks both call:
msg = await ws.receive()
# after
# single reader fans out to a queue
async def reader(ws, q):
async for msg in ws:
if msg.type == WSMsgType.TEXT:
await q.put(msg.data)
async for data in consumer_queue:
handle(data) Defensive patterns
Strategy: validation
Validate before calling
import asyncio
read_lock = asyncio.Lock()
async def safe_receive(ws):
async with read_lock:
return await ws.receive() Try / catch
try:
msg = await ws.receive()
except RuntimeError as e:
if "Concurrent call to receive()" in str(e):
await asyncio.sleep(0)
return await ws.receive()
raise Prevention
- Have exactly one consumer coroutine call receive()/async-for on each WebSocketResponse.
- Fan out received messages via an asyncio.Queue rather than multiple readers.
- Use an asyncio.Lock if multiple tasks must coordinate reads.
When it happens
Trigger: Two coroutines both awaiting `ws.receive()` (or `async for msg in ws`) on the same WebSocketResponse at the same time. The check at aiohttp/web_ws.py:604-605 sees `_waiting` already True.
Common situations: A background task calling receive() while the main handler loop also calls receive(); mixing manual receive() calls with `async for` iteration; a heartbeat task that tries to receive; spawning multiple readers with asyncio.gather on the same ws.
Related errors
- WebSocket connection is closed.
- 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
- 1007
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/1b15c07728bce956.json.
Report an issue: GitHub.