#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.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top