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 potential

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Treat any earlier transport error/EOF as terminal: stop writing and close the writer instead of continuing the write loop
  2. Check writer.is_closing() before writing/draining and abort if true
  3. Catch ConnectionResetError (and BrokenPipeError) around drain and reconnect/retry at the protocol level
  4. 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

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


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/ccafcdbd53c77ae4. Report an issue: GitHub.