aio-libs/aiohttp · warning · ConnectionResetError
Connection lost
Error message
Connection lost
What it means
Raised as ConnectionResetError('Connection lost') in FileResponse._sendfile (aiohttp/web_fileresponse.py:137) when request.transport is None at the moment aiohttp is about to call loop.sendfile / aiofastnet.sendfile. A None transport means the underlying socket has already been closed (client disconnected or the protocol's transport was replaced) and there is no longer a file descriptor to send to. This is raised before any bytes are sent via sendfile(2).
Source
Thrown at aiohttp/web_fileresponse.py:137
break
chunk = await loop.run_in_executor(None, fobj.read, min(chunk_size, count))
await writer.drain()
return writer
async def _sendfile(
self, request: "BaseRequest", fobj: BinaryIO, offset: int, count: int
) -> AbstractStreamWriter:
writer = await super().prepare(request)
assert writer is not None
if NOSENDFILE or self.compression:
return await self._sendfile_fallback(writer, fobj, offset, count)
loop = request._loop
transport = request.transport
if transport is None:
raise ConnectionResetError("Connection lost")
try:
if aiofastnet is not None:
await aiofastnet.sendfile(loop, transport, fobj, offset, count)
else:
await loop.sendfile(transport, fobj, offset, count) # type: ignore[unreachable]
except NotImplementedError:
return await self._sendfile_fallback(writer, fobj, offset, count)
await super().write_eof()
return writer
@staticmethod
def _etag_match(etag_value: str, etags: tuple[ETag, ...], *, weak: bool) -> bool:
if len(etags) == 1 and etags[0].value == ETAG_ANY:
return True
return any(
etag.value == etag_value for etag in etags if weak or not etag.is_weakView on GitHub (pinned to c0ef574e29)
Solutions
- Treat ConnectionResetError / ConnectionError from FileResponse as expected and log at debug, not error - the client is already gone.
- Wrap file-serving handlers in try/except ConnectionError and return early.
- Tune upstream proxy timeouts (client_body_timeout, send_timeout) if disconnects happen mid-transfer on healthy clients.
- Verify the file path is valid before constructing FileResponse so unrelated failures don't masquerade as transport loss.
Example fix
// before
async def serve(request):
return web.FileResponse(path)
# after
async def serve(request):
try:
return web.FileResponse(path)
except (ConnectionResetError, ConnectionError):
logger.debug("client gone during FileResponse(%s)", path)
raise Defensive patterns
Strategy: try-catch
Validate before calling
if request.transport is None:
raise web.HTTPUnavailable() # client already gone Type guard
def transport_alive(request) -> bool:
return request.transport is not None Try / catch
try:
return web.FileResponse(path)
except (ConnectionResetError, ConnectionError):
logger.debug("client gone during FileResponse")
raise Prevention
- Treat ConnectionError from FileResponse as expected; log at debug, not error.
- Tune upstream proxy timeouts if disconnects are frequent on healthy clients.
- Validate file paths before construction so unrelated errors are not masked.
When it happens
Trigger: Serving a static file with web.FileResponse(path) after the client has closed the connection (e.g. user aborted the download, or a proxy timed out and tore down the TCP connection). The handler still ran to the point of preparing the response, but transport was reset to None by the time _sendfile looked at it.
Common situations: Mobile clients on flaky networks, browsers cancelling downloads, or downstream proxies (nginx, CloudFront) with aggressive client-header-buffer / read timeouts. Also reproducible in tests that close the test client before the handler reaches sendfile.
Related errors
- Cannot write to closing transport
- Cannot write to closing transport
- Connection lost
- Connection timeout to host {url}
- DNS lookup failed
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/8eec40b015078dca.json.
Report an issue: GitHub.