Django REST framework request timeout and client cancellation handling
0 reputation · 18 Sept 2026, 00:59 UTC
0 reputation · 18 Sept 2026, 00:59 UTC
Django REST framework APIView and generic views do not expose a built-in request timeout attribute. Timeout handling is typically delegated to the web server or custom middleware, which leaves the API layer without a documented cancellation signal.
Streaming responses are supported via StreamingHttpResponse, but detecting client disconnect is not described as core documented behavior. Third-party packages address the gap, with varying compatibility across async views and test environments.
The design question is where cancellation and timeout semantics should be enforced and how resource cleanup is coordinated when a request is terminated.
Does DRF provide a documented mechanism to detect client disconnect during view execution? Is client-initiated cancellation expected to be handled at the API layer or delegated to the server? How should resource cleanup be coordinated when a request is terminated by timeout or disconnect?
34775 reputation · 18 Sept 2026, 03:22 UTC
No: DRF (and Django generally) has no documented, portable request-timeout attribute and no core API-layer cancellation signal for classic synchronous views. Timeout enforcement is delegated to the web server (Gunicorn worker timeout, uWSGI harakiri, nginx proxy_read_timeout) or to code you write yourself. Client-disconnect detection is only a first-class, documented concept in async Django views via request.is_disconnected() (Django 4.2+). Cleanup is your responsibility, coordinated through context managers, generator close(), or try/finally blocks.
The confirmed facts: DRF's APIView and generic views expose no timeout setting; StreamingHttpResponse is the supported way to stream; and Django's async request object documents is_disconnected() as a coroutine you poll. Everything else — catching BrokenPipeError in sync views, relying on GeneratorExit when a streaming generator is closed — is common practice that depends on your server, not a DRF guarantee.
In a WSGI deployment (Gunicorn sync workers), the view usually cannot notice a disconnect until it tries to write. With StreamingHttpResponse, the server closing the generator raises GeneratorExit inside it, and writes after disconnect surface as BrokenPipeError or ConnectionResetError. That makes the generator's finally block the natural cleanup point:
def event_stream():
try:
while True:
yield "tick\n"
time.sleep(1)
finally:
release_resources() # runs on GeneratorExit tooCaveat: BrokenPipeError also occurs for genuine I/O faults. Log it, and only treat it as a client abort when it happens during response streaming. Also note that a Gunicorn sync worker killed by the server timeout gets no Python-level cleanup signal at all — the process is terminated — so server timeouts must be paired with idempotent, externally visible resource tracking (e.g., DB rows with leases) rather than in-process cleanup.
Under ASGI (Uvicorn, Daphne) with Django 4.2+, poll the disconnect signal and enforce your own timeout:
class LongView(APIView):
async def get(self, request):
try:
return await asyncio.wait_for(self._work(request), timeout=30)
except asyncio.TimeoutError:
return Response({"detail": "timeout"}, status=504)
async def _work(self, request):
while True:
if await request.is_disconnected():
raise asyncio.CancelledError
await asyncio.sleep(0.5)
# ... do work, cleanup in finally ...Wrap the work in try/finally (or async with) so cancellation and timeout both run the same cleanup. is_disconnected() does not exist on sync views or older Django — calling it there raises AttributeError.
--timeout, nginx timeouts) — they protect against wedged workers but give no cleanup hook.asyncio.wait_for in async; a thread-based deadline or Celery task timeouts in sync), where finally blocks can run.close() as your only signal.1. Sync: stream one line per second, run curl -s localhost:8000/stream | head -n 2, and confirm your generator's finally executes. 2. Async: have a client close the socket mid-request and confirm the is_disconnected() loop exits and cleanup runs. 3. Set a 3-second asyncio.wait_for around a 10-second sleep and confirm the timeout path and cleanup both fire. Behavior differs between WSGI and ASGI servers, so re-run these checks if you change deployment.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.