{"record":{"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/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_ws.py#L587-L623","documentation":"Raised by WebSocketResponse.receive() at the top of its loop when self._waiting is already True. aiohttp tracks an in-flight read because the WebSocket has a single shared reader queue and frame ordering would be corrupted by two concurrent consumers. The flag is set just before awaiting reader.read() and cleared in the finally block.","triggerScenarios":"Two tasks both calling `await ws.receive()` (or receive_str/receive_bytes/receive_json, which all call receive()) at the same time; an `async for msg in ws:` loop running while another task also calls receive(); a heartbeat/error handler that reads while the main loop reads.","commonSituations":"Spawning a second asyncio task to 'monitor' the socket while the main loop iterates messages; calling receive() from both the request handler and a cleanup task; re-entering receive() recursively.","solutions":["Serialize all reads through a single consumer task — one `async for msg in ws:` loop, nothing else calling receive().","If you must fan out, run one reader task that pushes messages into an asyncio.Queue and have workers read from that queue.","Ensure prior receive() calls are not still awaiting (e.g. a cancelled task that did not unwind its finally clearing _waiting).","Do not call receive_str/receive_bytes/receive_json from a different task than the main receive loop."],"exampleFix":"// before\nasyncio.gather(ws.receive(), ws.receive())  # raises in one\n\n// after\nasync def reader(ws, out):\n    async for msg in ws:\n        await out.put(msg)\nasyncio.create_task(reader(ws, queue))\n# workers consume from `queue`, never call ws.receive()","handlingStrategy":"try-catch","validationCode":"# There is no safe pre-check; the only valid design is a single reader.\n# Run exactly one task that calls ws.receive() and routes results via an asyncio.Queue.","typeGuard":"def ws_is_idle(ws: web.WebSocketResponse) -> bool:\n    return not getattr(ws, '_waiting', False)  # informational only; races in concurrent code","tryCatchPattern":"try:\n    msg = await ws.receive()\nexcept RuntimeError as e:\n    if 'Concurrent call to receive' in str(e):\n        # route this reader through a shared queue instead of retrying\n        raise\n    raise","preventionTips":["Have exactly one task reading from the WebSocket; fan messages out through an asyncio.Queue.","Never call receive_str/receive_bytes/receive_json from a different task than the main loop.","Ensure a cancelled receive() unwinds fully (its finally clears _waiting) before starting another."],"tags":["websocket","concurrency","receive","server"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}