Compare commits

...

2 Commits

Author SHA1 Message Date
Marcelo Trylesinski
ecedca1011
Apply suggestion from @Kludex 2026-02-15 22:43:41 +01:00
Marcelo Trylesinski
3e1224cbe3 Use context= kwarg on loop.create_task for Python 3.11+
On Python 3.11+, `loop.create_task` accepts a `context` kwarg
directly, avoiding the `Context().run()` indirection. This was
already noted as a TODO in the codebase.

Ref: https://github.com/encode/uvicorn/discussions/2794
2026-02-15 22:42:19 +01:00
2 changed files with 14 additions and 12 deletions

View File

@ -4,6 +4,7 @@ import asyncio
import contextvars
import http
import logging
import sys
from collections.abc import Callable
from typing import Any, Literal, cast
from urllib.parse import unquote
@ -249,12 +250,12 @@ class H11Protocol(asyncio.Protocol):
message_event=asyncio.Event(),
on_response=self.on_response_complete,
)
# For the asyncio loop, we need to explicitly start with an empty context
# as it can be polluted from previous ASGI runs.
# See https://github.com/python/cpython/issues/140947 for details.
task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))
# TODO: Replace the line above with the line below for Python >= 3.11
# task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())
# Start with an empty context to prevent context variable pollution
# between ASGI runs. See https://github.com/python/cpython/issues/140947
if sys.version_info >= (3, 11): # pragma: no cover
task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())
else: # pragma: no cover
task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))
task.add_done_callback(self.tasks.discard)
self.tasks.add(task)

View File

@ -5,6 +5,7 @@ import contextvars
import http
import logging
import re
import sys
import urllib
from asyncio.events import TimerHandle
from collections import deque
@ -289,12 +290,12 @@ class HttpToolsProtocol(asyncio.Protocol):
)
if existing_cycle is None or existing_cycle.response_complete:
# Standard case - start processing the request.
# For the asyncio loop, we need to explicitly start with an empty context
# as it can be polluted from previous ASGI runs.
# See https://github.com/python/cpython/issues/140947 for details.
task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))
# TODO: Replace the line above with the line below for Python >= 3.11
# task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())
# Start with an empty context to prevent context variable pollution
# between ASGI runs. See https://github.com/python/cpython/issues/140947
if sys.version_info >= (3, 11): # pragma: no cover
task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())
else: # pragma: no cover
task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))
task.add_done_callback(self.tasks.discard)
self.tasks.add(task)
else: