aio-libs/aiohttp · error · RuntimeError
() called while another coroutine is already waiting for…
Error message
%s() called while another coroutine is already waiting for incoming data
What it means
Raised by StreamReader._wait when self._waiter is already an unresolved Future, meaning another coroutine is currently awaiting incoming data on the same reader. StreamReader links the protocol's feed_data to a single read coroutine via one waiter future; two concurrent readers would race unpredictably for each chunk, so aiohttp forbids it.
Solutions
- Serialize all reads of one stream through a single consumer task; do not share a StreamReader between concurrent coroutines.
- If you need fan-out, have one reader push chunks into an asyncio.Queue and have workers read from the queue.
- Read the whole body once (await response.read()) and then distribute the bytes.
Example fix
# before
async def worker(c):
return await c.read(100)
await asyncio.gather(worker(content), worker(content))
# after
body = await response.read() # single consumer
# split body between workers as needed Defensive patterns
Strategy: validation
Validate before calling
if stream._waiter is not None and not stream._waiter.done():
raise RuntimeError('another reader is active on this stream')
data = await stream.read(n) Type guard
def stream_idle(stream) -> bool:
w = getattr(stream, '_waiter', None)
return w is None or w.done() Try / catch
try:
data = await stream.read(n)
except RuntimeError as e:
if 'already waiting' in str(e):
# serialize reads instead of concurrent
raise
raise Prevention
- Never share a single StreamReader across concurrent tasks.
- Read each stream through exactly one consumer coroutine.
- Fan out via asyncio.Queue fed by the single reader.
When it happens
Trigger: Two tasks both calling await content.read(n) (or readline/readchunk) on the same response/request stream at the same time; a background task draining the body while the main task also reads; calling read while a previous read is still suspended.
Common situations: Spawning asyncio.gather over the same stream; a progress-monitoring task reading the body alongside the consumer; reusing a single response across multiple worker tasks.
Related errors
- Called while some coroutine is waiting for incoming data.
- EmptyStreamReader._on_chunk_received is read-only
- Unable to decode - content not cached. Call as_bytes()…
- Cannot call .write() for websocket
- Concurrent call to receive() is not allowed
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/ecaf8b5b8bcaaaf9.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/streams.py:358
if len(self._http_chunk_splits) > self._high_water_chunks:
self._protocol.pause_reading()
# wake up readchunk when end of http chunk received
waiter = self._waiter
if waiter is not None:
self._waiter = None
set_result(waiter, None)
async def _wait(self, func_name: str) -> None:
if not self._protocol.connected:
raise RuntimeError("Connection closed.")
# StreamReader uses a future to link the protocol feed_data() method
# to a read coroutine. Running two read coroutines at the same time
# would have an unexpected behaviour. It would not possible to know
# which coroutine would get the next data.
if self._waiter is not None:
raise RuntimeError(
"%s() called while another coroutine is "
"already waiting for incoming data" % func_name
)
waiter = self._waiter = self._loop.create_future()
try:
with self._timer:
await waiter
finally:
self._waiter = None
async def _fire_chunk_received(self, chunk: bytes) -> None:
cb = self._on_chunk_received
assert cb is not None
# Run under the same per-stream timer that _wait() uses, so a hung
# trace handler is bounded by sock_read just like a hung socket read would be.
with self._timer:
await cb(chunk)View on GitHub (pinned to d041d4d0fd)