Skip to content

Add --reset-contextvars flag to isolate ASGI request context#2912

Merged
Kludex merged 6 commits intomainfrom
add-reset-contextvars-flag
Apr 21, 2026
Merged

Add --reset-contextvars flag to isolate ASGI request context#2912
Kludex merged 6 commits intomainfrom
add-reset-contextvars-flag

Conversation

@Kludex
Copy link
Copy Markdown
Owner

@Kludex Kludex commented Apr 21, 2026

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 fresh contextvars.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 uvicorn
  • uv run pytest (100% coverage)
  • New test: context set outside the handler is visible inside by default
  • New test: with 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.

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.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

📖 Docs preview: https://f63a79d9.uvicorn.pages.dev

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 21, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing add-reset-contextvars-flag (98ec016) with main (45c0b56)

Open in CodSpeed

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +290 to +293
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:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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.
@Kludex Kludex merged commit 73a80c3 into main Apr 21, 2026
42 of 44 checks passed
@Kludex Kludex deleted the add-reset-contextvars-flag branch April 21, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant