aio-libs/aiohttp · error · RuntimeError
Connection closed.
Error message
Connection closed.
What it means
Raised by StreamReader._wait when self._protocol.connected is False at the moment a read coroutine is about to await new data. It means the underlying transport/protocol has disconnected, so blocking for more data would hang forever; aiohttp raises instead. This typically surfaces wrapped in a ClientPayloadError or ServerDisconnectedError depending on context.
Solutions
- Treat a fully-read response as done: check response.content.at_eof() before reading more.
- Catch the error (often as aiohttp.ClientPayloadError or ServerDisconnectedError) and retry the request or close cleanly.
- Ensure you read the body exactly once and within the response context manager.
Example fix
# before
data = await response.content.read(1024) # may raise after disconnect
# after
if not response.content.at_eof():
data = await response.content.read(1024)
else:
data = b'' Defensive patterns
Strategy: validation
Validate before calling
if response.content.at_eof() or not response.content._protocol.connected:
data = b''
else:
data = await response.content.read(n) Type guard
def stream_readable(stream) -> bool:
return getattr(stream, '_protocol', None) is not None and stream._protocol.connected and not stream.at_eof() Try / catch
try:
data = await response.content.read(n)
except (RuntimeError, aiohttp.ClientPayloadError, aiohttp.ServerDisconnectedError):
data = b'' # connection gone; handle partial/no data Prevention
- Always read response bodies within the async with response context.
- Check at_eof() before requesting more data in a loop.
- Handle ClientPayloadError to tolerate mid-stream disconnects.
When it happens
Trigger: Calling await response.content.read(n), readline(), or readuntil() after the server closed the connection (or after the response body was already fully consumed and the protocol detached); reading a request body on the server side after the client disconnected.
Common situations: Server closed keep-alive connection between reads; reading past Content-Length; proxy/load-balancer dropping an idle connection; client disconnect during server-side request body streaming.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/4787f5a133a8711f.
Report an issue: GitHub.
Appendix: 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 d041d4d0fd)