aio-libs/aiohttp · error · ClientConnectionResetError

Cannot write to closing transport

Error message

Cannot write to closing transport

What it means

Raised as ClientConnectionResetError when StreamWriter._write attempts to write a chunk but the underlying transport is None or is closing (transport.is_closing()). The guard runs before every transport.write in the synchronous _write path. It indicates the connection is shutting down and further writes are unsafe.

Solutions

  1. Guard writes: check request.transport.is_closing() (or the writer) before writing, and bail out cleanly.
  2. Catch ClientConnectionResetError / ConnectionResetError around write+drain and treat as a normal client-disconnect.
  3. Avoid background tasks that outlive the response; cancel them on cleanup.
  4. Use drain() and handle CancelledError to stop writing promptly when the client goes away.
  5. For long streams, detect disconnect early (e.g. via disconnector wait) and abort the producer.

Example fix

# before
async def handler(request):
    while True:
        await request.transport.write(data)  # may raise after client gone

# after
async def handler(request):
    try:
        resp = web.StreamResponse()
        await resp.prepare(request)
        while True:
            await resp.write(data)
            await resp.drain()
    except (ClientConnectionResetError, ConnectionResetError):
        return resp  # client gone, stop quietly
Defensive patterns

Strategy: try-catch

Validate before calling

def can_write(transport) -> bool:
    return transport is not None and not transport.is_closing()

Try / catch

from aiohttp import ClientConnectionResetError

async def safe_write(writer, chunk):
    try:
        await writer.write(chunk)
        await writer.drain()
    except ClientConnectionResetError:
        # client disconnected; stop producing
        return False
    return True

Prevention

When it happens

Trigger: The server handler (or client writer) calls write()/StreamWriter._write after the transport has started closing — e.g. the peer closed/reset the connection, or write_eof was issued, and a subsequent write is attempted.

Common situations: A handler writing a response after the client disconnected (slow client / aborted request); pipelined writes racing with connection close; a write scheduled by a background task after the response finished; keep-alive timeout firing mid-write; the peer sending a RST/FIN.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/98ba53ab7e144d7b. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/http_writer.py:103

        return self._protocol

    def enable_chunking(self) -> None:
        self.chunked = True

    def enable_compression(
        self, encoding: str = "deflate", strategy: int | None = None
    ) -> None:
        self._compress = ZLibCompressor(encoding=encoding, strategy=strategy)

    def _write(
        self, chunk: Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]
    ) -> None:
        size = len(chunk)
        self.buffer_size += size
        self.output_size += size
        transport = self._protocol.transport
        if transport is None or transport.is_closing():
            raise ClientConnectionResetError("Cannot write to closing transport")
        transport.write(chunk)

    def _writelines(
        self,
        chunks: Iterable[
            Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]
        ],
    ) -> None:
        size = 0
        for chunk in chunks:
            size += len(chunk)
        self.buffer_size += size
        self.output_size += size
        transport = self._protocol.transport
        if transport is None or transport.is_closing():
            raise ClientConnectionResetError("Cannot write to closing transport")
        if SKIP_WRITELINES or size < MIN_PAYLOAD_FOR_WRITELINES:
            transport.write(b"".join(chunks))

View on GitHub (pinned to d041d4d0fd)