{"id":"ecaf8b5b8bcaaaf9","repo":"aio-libs/aiohttp","slug":"s-called-while-another-coroutine-is-already-wai","errorCode":null,"errorMessage":"%s() called while another coroutine is already waiting for incoming data","messagePattern":"(.+?)\\(\\) called while another coroutine is already waiting for incoming data","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/streams.py","lineNumber":358,"sourceCode":"        if len(self._http_chunk_splits) > self._high_water_chunks:\n            self._protocol.pause_reading()\n\n        # wake up readchunk when end of http chunk received\n        waiter = self._waiter\n        if waiter is not None:\n            self._waiter = None\n            set_result(waiter, None)\n\n    async def _wait(self, func_name: str) -> None:\n        if not self._protocol.connected:\n            raise RuntimeError(\"Connection closed.\")\n\n        # StreamReader uses a future to link the protocol feed_data() method\n        # to a read coroutine. Running two read coroutines at the same time\n        # would have an unexpected behaviour. It would not possible to know\n        # which coroutine would get the next data.\n        if self._waiter is not None:\n            raise RuntimeError(\n                \"%s() called while another coroutine is \"\n                \"already waiting for incoming data\" % func_name\n            )\n\n        waiter = self._waiter = self._loop.create_future()\n        try:\n            with self._timer:\n                await waiter\n        finally:\n            self._waiter = None\n\n    async def _fire_chunk_received(self, chunk: bytes) -> None:\n        cb = self._on_chunk_received\n        assert cb is not None\n        # Run under the same per-stream timer that _wait() uses, so a hung\n        # trace handler is bounded by sock_read just like a hung socket read would be.\n        with self._timer:\n            await cb(chunk)","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/streams.py#L340-L376","documentation":"Raised by StreamReader._wait when self._waiter is already a pending future. aiohttp binds each chunk of incoming data to exactly one outstanding read coroutine; a second concurrent reader would make chunk delivery nondeterministic. Any of read/readany/readline/readchunk/readexactly calls that block at the same time therefore trip this RuntimeError. The message interpolates the offending method name via %s (func_name).","triggerScenarios":"Two coroutines reading the same request.content simultaneously (e.g. one task reads the body, another logs chunks); awaiting request.read() while an asyncio Task also iterates request.content; mixing manual readchunk() with read() on the same stream.","commonSituations":"Background logging/metering task competing with the request handler for the body; firing multiple awaits in gather() against one stream; middleware that reads the body while the handler also does; accidental double-await of the same coroutine object.","solutions":["Serialize reads: have a single consumer drain the stream, then distribute data to others.","If you must tee, buffer the body once: `body = await request.read()` then reuse the bytes.","Cancel competing read tasks before issuing a new read.","Use asyncio.Lock around stream access if you cannot guarantee single-consumer."],"exampleFix":"// before\nbody = await asyncio.gather(\n    request.content.read(),\n    log_chunks(request.content),\n)  # two concurrent readers\n// after\nbody = await request.read()\nawait log_chunks_from(body)","handlingStrategy":"validation","validationCode":"# guarantee single-consumer with a lock:\nread_lock = asyncio.Lock()\n\nasync def safe_read(stream, n=-1):\n    async with read_lock:\n        return await stream.read(n)","typeGuard":null,"tryCatchPattern":"try:\n    return await stream.read(n)\nexcept RuntimeError as e:\n    if 'another coroutine is already waiting' in str(e):\n        raise ConcurrentReadError('serialize stream reads') from e\n    raise","preventionTips":["Designate exactly one consumer per StreamReader.","If you must fan-out, materialize body bytes once and share them.","Use asyncio.Lock when consumers cannot be statically serialized."],"tags":["streams","concurrency","asyncio"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}