aio-libs/aiohttp · error · RuntimeError
Connection closed.
Error message
Connection closed.
What it means
Raised by StreamReader._wait when self._protocol.connected is False — i.e. a read coroutine (read/readany/readline/readchunk) is invoked after the transport has already closed. aiohttp refuses to await a future that can never be resolved because there is no live connection to feed it. The proper signal for end-of-stream is EOF (at_eof()), not a RuntimeError, so this is a genuine misuse.
Source
Thrown at aiohttp/streams.py:351
return
self._http_chunk_splits.append(self.total_bytes)
# If we get too many small chunks before self._high_water is reached, then any
# .read() call becomes computationally expensive, and could block the event loop
# for too long, hence an additional self._high_water_chunks here.
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
View on GitHub (pinned to c0ef574e29)
Solutions
- Read the body inside the `async with session.get(...) as r:` block before it exits.
- Check `await request.can_read_body()` / `request.body_exists` before reading.
- Catch RuntimeError and treat as end-of-stream if your flow tolerates abrupt close.
- Ensure no background task outlives the response context manager.
Example fix
// before
async with session.get(url) as r:
data = r # captured
return await data.content.read() # connection already closed
// after
async with session.get(url) as r:
return await r.read() Defensive patterns
Strategy: validation
Validate before calling
# check liveness before reading:
if not request.content.is_eof() and not request.content._protocol.connected:
raise ClientConnectionError('connection already closed')
if request.content.at_eof():
return b''
return await request.content.read() Try / catch
try:
chunk = await stream.read(n)
except RuntimeError as e:
if 'Connection closed' in str(e):
return b'' # treat as EOF
raise Prevention
- Always read the body inside the response context manager.
- Check request.content.at_eof() / can_read_body() before reading.
- Do not retain references to request/response across lifecycles.
When it happens
Trigger: Calling await request.content.read(...) after request.release()/response close; reading a client response body after the session or connection was closed; using the same StreamReader from two lifecycles; reading after the server dropped the connection without sending EOF.
Common situations: Forgetting to keep the ClientSession / response context manager open while background task reads; calling read() in a finally block after the connection broke; reusing a request object across requests; webhook handler reading body after returning a response that closed the connection.
Related errors
- Cannot write to closing transport
- Connection lost
- Connection timeout to host {url}
- Session is closed
- Connection closed
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/4787f5a133a8711f.json.
Report an issue: GitHub.