python/cpython · error · ConnectionResetError
Connection lost
Error message
Connection lost
What it means
ConnectionResetError('Connection lost') is raised by StreamWriter.drain() (via FlowControlMixin._drain_helper) when the underlying transport has already flagged _connection_lost. It is asyncio's way of surfacing, at the next await point, that the peer closed or reset the connection after you wrote data. Drain is the only write-path await, so this is where pending connection loss is reported.
Source
Thrown at Lib/asyncio/streams.py:166
if not waiter.done():
waiter.set_result(None)
def connection_lost(self, exc):
self._connection_lost = True
# Wake up the writer(s) if currently paused.
if not self._paused:
return
for waiter in self._drain_waiters:
if not waiter.done():
if exc is None:
waiter.set_result(None)
else:
waiter.set_exception(exc)
async def _drain_helper(self):
if self._connection_lost:
raise ConnectionResetError('Connection lost')
if not self._paused:
return
waiter = self._loop.create_future()
self._drain_waiters.append(waiter)
try:
await waiter
finally:
self._drain_waiters.remove(waiter)
def _get_close_waiter(self, stream):
raise NotImplementedError
class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
"""Helper class to adapt between Protocol and StreamReader.
(This is a helper class instead of making StreamReader itself a
Protocol subclass, because the StreamReader has other potentialView on GitHub (pinned to bc6749cc3b)
Solutions
- Treat any earlier transport error/EOF as terminal: stop writing and close the writer instead of continuing the write loop
- Check writer.is_closing() before writing/draining and abort if true
- Catch ConnectionResetError (and BrokenPipeError) around drain and reconnect/retry at the protocol level
- Enable keepalives or heartbeats so a dead connection is detected before you write
Example fix
// before
writer.write(payload)
await writer.drain()
// after
if writer.is_closing():
raise ConnectionResetError('local writer already closing')
writer.write(payload)
try:
await writer.drain()
except (ConnectionResetError, BrokenPipeError):
writer.close()
raise Defensive patterns
Strategy: try-catch
Validate before calling
if writer.is_closing():
raise ConnectionResetError('writer already closing; do not write') Type guard
null
Try / catch
try:
writer.write(payload)
await writer.drain()
except (ConnectionResetError, BrokenPipeError):
writer.close()
await reconnect_and_resend(payload) # app-level recovery Prevention
- Check writer.is_closing() before every write/drain in long-lived connections
- Stop the write loop as soon as protocol.connection_lost() or any transport error fires
- Add heartbeats/keepalive so dead peers are detected before the next write
When it happens
Trigger: Calling await writer.write(...) then await writer.drain() after the transport received connection_lost (peer sent RST/FIN, socket error, or transport.abort() was called); writing after protocol.connection_lost() fired.
Common situations: HTTP or RPC clients that keep writing requests after the server closed the idle connection; servers continuing to stream after client disconnect; mobile/flaky networks where the reset arrives mid-request; code that ignores earlier transport errors and keeps using the writer.
Related errors
- no matching local address with {family=} found
- server_hostname is only meaningful with ssl
- host/port and sock can not be specified at the same time
- getaddrinfo() returned empty list
- create_connection failed
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/ccafcdbd53c77ae4.
Report an issue: GitHub.