{"id":"1b15c07728bce956","repo":"aio-libs/aiohttp","slug":"concurrent-call-to-receive-is-not-allowed-1b15c0","errorCode":null,"errorMessage":"Concurrent call to receive() is not allowed","messagePattern":"Concurrent call to receive\\(\\) is not allowed","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":605,"sourceCode":"    async def receive(\n        self: \"WebSocketResponse[Literal[False]]\", timeout: float | None = None\n    ) -> WSMessageNoDecodeText: ...\n\n    @overload\n    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):","sourceCodeStart":587,"sourceCodeEnd":623,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_ws.py#L587-L623","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\n# two tasks both call:\nmsg = await ws.receive()\n\n# after\n# single reader fans out to a queue\nasync def reader(ws, q):\n    async for msg in ws:\n        if msg.type == WSMsgType.TEXT:\n            await q.put(msg.data)\n\nasync for data in consumer_queue:\n    handle(data)","handlingStrategy":"validation","validationCode":"import asyncio\n\nread_lock = asyncio.Lock()\n\nasync def safe_receive(ws):\n    async with read_lock:\n        return await ws.receive()","typeGuard":null,"tryCatchPattern":"try:\n    msg = await ws.receive()\nexcept RuntimeError as e:\n    if \"Concurrent call to receive()\" in str(e):\n        await asyncio.sleep(0)\n        return await ws.receive()\n    raise","preventionTips":["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."],"tags":["websocket","server","concurrency","receive"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}