Skip to content

fix: propagate contextvars to event handlers#3057

Open
Skn0tt wants to merge 1 commit intomicrosoft:mainfrom
Skn0tt:fix/contextvars-event-handlers
Open

fix: propagate contextvars to event handlers#3057
Skn0tt wants to merge 1 commit intomicrosoft:mainfrom
Skn0tt:fix/contextvars-event-handlers

Conversation

@Skn0tt
Copy link
Copy Markdown
Member

@Skn0tt Skn0tt commented Apr 30, 2026

Fixes #1816

Capture the caller's contextvars context when an event handler is registered, and re-establish it when the handler runs. Without this, contextvars set in user code (e.g. request IDs in logging frameworks) were not visible inside event handlers because events are dispatched from a different greenlet (sync mode) or asyncio task (async mode) than the one that registered the handler.

Implementation per mode

  • Async-mode coroutine handler — spawn an inner Task inside the captured context so the Task adopts it (Tasks copy the active context at construction).
  • Async-mode sync handler — run via Context.run.
  • Sync mode — temporarily set the EventGreenlet's gr_context to the captured context for the duration of the handler. Context.run is not used here because handlers like route.fulfill internally call greenlet.switch, and Context.run does not compose with greenlet switches (it would unwind on switch and corrupt context state on switch-back).

Tests

Added regression tests for both sync and async modes that set a ContextVar, register a request handler, navigate, and assert the handler observed the value. The async test also asserts the value survives an await asyncio.sleep(0) boundary inside the handler.

Verification

  • New tests pass.
  • Full sync suite: 671/671 pass.
  • Full async suite: 1521/1521 pass.

Capture the caller's contextvars context when an event handler is
registered, and re-establish it when the handler runs. Without this,
contextvars set in user code (e.g. request IDs in logging frameworks)
were not visible inside event handlers because events are dispatched
from a different greenlet (sync mode) or asyncio task (async mode)
than the one that registered the handler.

Implementation per mode:
  * Async-mode coroutine handler: spawn an inner Task inside the
    captured context so the Task adopts it (Tasks copy the active
    context at construction).
  * Async-mode sync handler: run via Context.run.
  * Sync mode: temporarily set the EventGreenlet's gr_context to the
    captured context for the duration of the handler. Context.run is
    not used here because handlers like route.fulfill internally
    greenlet.switch, and Context.run does not compose with greenlet
    switches.

Fixes microsoft#1816

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Skn0tt Skn0tt requested a review from dgozman April 30, 2026 13:51
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.

[Feature] Propagate contextvars to event handlers

1 participant