aio-libs/aiohttp · error · ClientConnectionResetError
Connection lost
Error message
Connection lost
What it means
Raised as ClientConnectionResetError('Connection lost') in BaseProtocol._drain_helper when self.transport is None — i.e. connection_lost() has already run and nulled the transport, but a drain (flow-control wait) is still attempted. (The companion path at line 127 sets ConnectionError('Connection lost') on a pending drain waiter when connection_lost receives an exception.) This signals the underlying connection is gone and buffered writes cannot be flushed.
Solutions
- Catch ClientConnectionResetError / ConnectionError around send and drain operations and move the app to a disconnected state.
- Reduce the chance of pausing by keeping individual messages reasonably sized, and await send_frame so backpressure is observed.
- Implement a reconnect-with-backoff loop for resilient clients.
- Cancel outstanding writers when a connection-lost signal is received.
Example fix
// before
await ws.send_str(huge_payload) // drain after transport lost -> error
// after
try:
await ws.send_str(huge_payload)
except (ClientConnectionResetError, ConnectionError):
await reconnect_with_backoff() Defensive patterns
Strategy: try-catch
Validate before calling
// Check connection liveness before issuing large/backpressured writes.
if ws._writer.transport is None or ws.closed:
raise ClientConnectionResetError('no transport')
Type guard
def connection_alive(ws) -> bool:
t = getattr(ws._writer, 'transport', None)
return t is not None and not ws.closed
Try / catch
try:
await ws.send_str(payload)
except (ClientConnectionResetError, ConnectionError):
await reconnect_with_backoff()
Prevention
- Implement reconnect-with-backoff for resilient clients.
- Cancel outstanding writers on connection-lost signals.
- Keep individual writes reasonably sized to reduce drain pressure.
When it happens
Trigger: send_frame triggers flow-control drain (self._output_size > self._limit and protocol._paused), and _drain_helper is called after connection_lost already set self.transport = None. Equivalently, a drain waiter is resolved with ConnectionError when connection_lost receives an exc.
Common situations: The peer disconnects while aiohttp is paused on flow control and then tries to drain; large uploads where the transport closes mid-stream; network interruptions / RST during heavy traffic; server shutting down while client writes; the writer's output buffer exceeded the limit exactly as the connection dropped.
Related errors
- Cannot write to closing transport
- Connection lost
- Connection lost
- Cannot connect to unix socket
- Cannot write to closing transport
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/5003235d250ece0e.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/base_protocol.py:133
return
waiter = self._drain_waiter
if waiter is None:
return
self._drain_waiter = None
if waiter.done():
return
if exc is None:
waiter.set_result(None)
else:
set_exception(
waiter,
ConnectionError("Connection lost"),
exc,
)
async def _drain_helper(self) -> None:
if self.transport is None:
raise ClientConnectionResetError("Connection lost")
if not self._paused:
return
waiter = self._drain_waiter
if waiter is None:
waiter = self._loop.create_future()
self._drain_waiter = waiter
await waiter
View on GitHub (pinned to d041d4d0fd)