Opened 71 minutes ago
#37385 new Cleanup/optimization
ASGI client disconnects log "CancelledError exception in shielded future" on Python 3.14+
| Reported by: | Natalia Bidart | Owned by: | |
|---|---|---|---|
| Component: | HTTP handling | Version: | dev |
| Severity: | Release blocker | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Since a013c821ea (#37177), built-in middleware are sync-only, so under ASGI the default middleware chain runs in a thread and calls the view back through async_to_sync(). On Python 3.14+, a client disconnecting while the view is still running makes the asyncio logger emit an ERROR with a traceback:
CancelledError exception in shielded future
...
File ".../asgiref/sync.py", line 526, in __call__
ret = await asyncio.shield(exec_coro)
asyncio.exceptions.CancelledError
The request itself is handled correctly. Python 3.14 changed asyncio.shield() to log the shielded future's exception if the outer task was cancelled, and asgiref's SyncToAsync shields the executor future, which ends with CancelledError when the handler cancels the request.
To reproduce, in a startproject project with the default MIDDLEWARE, add:
async def slow_async(request): await asyncio.sleep(3) return HttpResponse("ok") def slow_sync(request): time.sleep(3) return HttpResponse("ok") urlpatterns += [ path("slow-async/", slow_async), path("slow-sync/", slow_sync), ]
Serve it with an async server (uvicorn mysite.asgi:application) and run curl --max-time 1 http://127.0.0.1:8000/slow-async/ (or /slow-sync/). The traceback is logged once the view finishes. The asgi tests also show it (./runtests.py asgi --parallel=1), though they pass.