ConnectionResetError
builtins.ConnectionResetError
Stack trace
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 429, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/usr/local/lib/python3.10/site-packages/fastapi/applications.py", line 276, in __call__
await super().__call__(scope, receive, send)
File "/usr/local/lib/python3.10/site-packages/starlette/applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "/usr/local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
raise exc
File "/usr/local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.10/site-packages/starlette/middleware/base.py", line 45, in __call__
await response(scope, receive, send)
File "/usr/local/lib/python3.10/site-packages/starlette/responses.py", line 338, in __call__
async for chunk in self.body_iterator:
File "/app/streaming.py", line 42, in event_generator
yield f"data: {json.dumps(data)}\n\n"
File "/usr/local/lib/python3.10/asyncio/streams.py", line 178, in write
self._transport.write(data)
File "/usr/local/lib/python3.10/asyncio/selector_events.py", line 103, in write
self._sock.send(data)
ConnectionResetError: [Errno 104] Connection reset by peer Why it happens
FastAPI SSE streams rely on long-lived HTTP connections. If the client closes the connection or network issues occur, the server's attempt to send events raises ConnectionResetError. Additionally, blocking operations in the event loop can delay heartbeats causing client disconnects.
Detection
Monitor server logs for ConnectionResetError during SSE streaming and track client disconnect events. Use middleware or logging to capture dropped connections before they cause failures.
Causes & fixes
Client closes the SSE connection unexpectedly (e.g., user navigates away or network drops)
Catch ConnectionResetError in the event generator and gracefully stop streaming without crashing the server.
Blocking synchronous code in the async event loop delays sending keep-alive events
Refactor blocking code to async or run it in a separate thread/process to keep the event loop responsive.
No periodic heartbeat or comment lines sent to keep the SSE connection alive
Send regular SSE comment lines (e.g., ': keep-alive\n\n') every 15-30 seconds to prevent proxies or clients from closing the connection.
Improperly configured server or reverse proxy timeouts close idle SSE connections
Configure server and proxy timeouts (e.g., nginx proxy_read_timeout) to allow long-lived SSE connections.
Code: broken vs fixed
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
app = FastAPI()
async def event_generator():
data = {"message": "hello"}
yield f"data: {json.dumps(data)}\n\n" # This line can raise ConnectionResetError
@app.get("/stream")
async def stream():
return StreamingResponse(event_generator(), media_type="text/event-stream") import os
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
import asyncio
app = FastAPI()
async def event_generator():
data = {"message": "hello"}
try:
yield f"data: {json.dumps(data)}\n\n" # Wrapped yield in try/except to handle disconnects
except ConnectionResetError:
# Client disconnected, stop streaming gracefully
return
@app.get("/stream")
async def stream():
return StreamingResponse(event_generator(), media_type="text/event-stream")
# No hardcoded keys needed here
print("FastAPI SSE streaming server ready") Workaround
Wrap the SSE event generator's yield statements in try/except ConnectionResetError blocks and log disconnects to avoid crashing the server.
Prevention
Implement periodic SSE heartbeat comments, avoid blocking code in the event loop, and configure server/proxy timeouts to support long-lived SSE connections.