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
- Guard writes: check request.transport.is_closing() (or the writer) before writing, and bail out cleanly.
- Catch ClientConnectionResetError / ConnectionResetError around write+drain and treat as a normal client-disconnect.
- Avoid background tasks that outlive the response; cancel them on cleanup.
- Use drain() and handle CancelledError to stop writing promptly when the client goes away.
- 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
- Check transport.is_closing() before writing in long-lived handlers.
- Catch ClientConnectionResetError around write/drain and stop cleanly.
- Cancel producer tasks on disconnect; do not let them outlive the response.
- Handle CancelledError to abort writes promptly.
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
- Connection lost
- Connection lost
- Cannot write to closing transport
- Connection lost
- Forbidden control character detected in headers. Potential…
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)