Add --reset-contextvars flag to isolate ASGI request context#2912
Add --reset-contextvars flag to isolate ASGI request context#2912
--reset-contextvars flag to isolate ASGI request context#2912Conversation
Reintroduces the behavior from #2742 as an opt-in flag. When set, each ASGI request runs in a fresh contextvars.Context, which works around the asyncio context leak in python/cpython#140947 (expected to be fixed upstream for Python >=3.15 via python/cpython#141158). Default is off so context set in the lifespan or by external instrumentation (e.g. OpenTelemetry eBPF propagation) remains visible to ASGI handlers.
|
📖 Docs preview: https://f63a79d9.uvicorn.pages.dev |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78754208f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if existing_cycle is None or existing_cycle.response_complete: | ||
| # Standard case - start processing the request. | ||
| task = self.loop.create_task(self.cycle.run_asgi(app)) | ||
| if self.config.reset_contextvars: | ||
| # Opt-in workaround for https://github.com/python/cpython/issues/140947: |
There was a problem hiding this comment.
Reset context for pipelined httptools requests
When reset_contextvars is enabled, this code only wraps task creation for the “standard case” request. If a second HTTP/1.1 request is pipelined before the first completes, it goes through the queued path and is later started in on_response_complete via plain self.loop.create_task(...), so it inherits the previous task context instead of a fresh one. That breaks the new flag’s guarantee (“each ASGI request runs in a fresh context”) for pipelined traffic and can reintroduce context leakage between requests on the same connection.
Useful? React with 👍 / 👎.
The pipelined-request resume path in `on_response_complete` created tasks with a plain `create_task`, bypassing the flag. Extract a `_start_asgi_task` helper so both the standard and pipelined paths share the same isolation logic.
Summary
Reintroduces the behavior from #2742 as an opt-in flag, after the revert in #2911.
When
reset_contextvars=True(CLI:--reset-contextvars), each ASGI request runs in a freshcontextvars.Context, which works around the asyncio context leak in python/cpython#140947. Default is off so context set in the lifespan or by external instrumentation (e.g. OpenTelemetry eBPF propagation) remains visible to ASGI handlers.python/cpython#141158 is expected to fix the underlying bug upstream for Python >=3.15, at which point this flag can be deprecated.
Test plan
uv run ruff check .uv run ruff format --check .uv run mypy uvicornuv run pytest(100% coverage)reset_contextvars=True, each request starts with an empty context (including the HIGH_WATER_LIMIT pause/resume path that originally exposed ContextVars pollution when uvicorn installed without [standard] extensions #2167)AI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.