GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — context variables can leak out of asyncio.Task · Issue #140947 · python/cpython

context variables can leak out of asyncio.Task #140947

New issue
Copy link
New issue
Copy link
Closed
Closed
context variables can leak out of asyncio.Task#140947
Copy link
Assignees
kumaraditya303
Labels
3.15bugs and security fixesbugs and security fixestopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@pmeier
pmeier
opened on Nov 3, 2025
Issue body actions

Bug report

Bug description:

This is a minimal reproducer for Kludex/uvicorn#2167. TL;DR when using asyncio as event loop, users of uvicorn were seeing polluted contexts after sending in large payloads. For those uvicorn paused reading on the main thread and resumed it in a task.

import asyncio
import contextvars
import sys

cvar1 = contextvars.ContextVar("cvar1")
cvar2 = contextvars.ContextVar("cvar2")
cvar3 = contextvars.ContextVar("cvar3")


def print_diagnostics(label):
    task = t.get_name() if (t := asyncio.current_task()) else None
    context = {c.name: v for c, v in contextvars.copy_context().items()}
    print(f"{label}: {task=}, {context=}\n{'-' * 80}")


class DemoProtocol(asyncio.Protocol):
    def __init__(self, on_conn_lost):
        self.transport = None
        self.on_conn_lost = on_conn_lost
        self.tasks = set()

    def connection_made(self, transport):
        print_diagnostics("connection_made")

        self.transport = transport

    def data_received(self, data):
        print_diagnostics("data_received")

        task = asyncio.create_task(self.asgi())
        self.tasks.add(task)
        task.add_done_callback(self.tasks.discard)

        self.transport.pause_reading()

    def connection_lost(self, exc):
        print_diagnostics("connection_lost")
        if not self.on_conn_lost.done():
            self.on_conn_lost.set_result(True)

    async def asgi(self):
        print_diagnostics("asgi start")

        cvar1.set(True)

        # make sure that we only resume after the pause
        # otherwise the resume does nothing
        while not self.transport._paused:
            await asyncio.sleep(0.1)

        cvar2.set(True)

        self.transport.resume_reading()

        cvar3.set(True)

        print_diagnostics("asgi end")


async def main():
    print(f"Python: {sys.version}\n{'-' * 80}")

    loop = asyncio.get_running_loop()
    on_conn_lost = loop.create_future()

    host, port = "127.0.0.1", 8888

    async with await loop.create_server(lambda: DemoProtocol(on_conn_lost), host, port):
        reader, writer = await asyncio.open_connection(host, port)
        writer.write(b"anything")
        await writer.drain()
        writer.close()
        await writer.wait_closed()
        await on_conn_lost


if __name__ == "__main__":
    asyncio.run(main())
Python: 3.14.0 (main, Oct 14 2025, 21:27:55) [Clang 20.1.4 ]
--------------------------------------------------------------------------------
connection_made: task=None, context={}
--------------------------------------------------------------------------------
data_received: task=None, context={}
--------------------------------------------------------------------------------
asgi start: task='Task-4', context={}
--------------------------------------------------------------------------------
asgi end: task='Task-4', context={'cvar2': True, 'cvar3': True, 'cvar1': True}
--------------------------------------------------------------------------------
connection_lost: task=None, context={'cvar2': True, 'cvar1': True}
--------------------------------------------------------------------------------

The asgi task sets three context variables:

  1. cvar1 is set at the beginning of the function, which may or may not be before the reading is paused.
  2. cvar2 is set after the reading is paused.
  3. cvar3 is set after the reading is resumed.

The context variables that have been set in the task before the reading is resumed (cvar1 and cvar2) leak out of the task into the main thread.

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Linked PRs

  • gh-140947: fix contextvars handling for server tasks in asyncio  #141158

Activity

  1. pmeier
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Nov 3, 2025
  2. kumaraditya303
    added
    topic-asyncio
    3.13only security fixesonly security fixes
    3.14bugs and security fixesbugs and security fixes
    3.15bugs and security fixesbugs and security fixes
    on Nov 3, 2025
  3. github-project-automation
    added this to asyncioon Nov 3, 2025
  4. github-project-automation
    moved this to Todo in asyncioon Nov 3, 2025
  5. kumaraditya303
    self-assigned this
    on Nov 3, 2025
  6. kumaraditya303 commented on Dec 14, 2025

    @kumaraditya303
    kumaraditya303
    on Dec 14, 2025
    Contributor
    More actions

    Output on Windows:

    ❯ ./python.bat main.py 
    Running Debug|x64 interpreter...
    Python: 3.15.0a2+ (heads/main:af7cca3c39d, Dec 14 2025, 16:45:59) [MSC v.1944 64 bit (AMD64)]
    --------------------------------------------------------------------------------
    connection_made: task=None, context={}
    --------------------------------------------------------------------------------
    data_received: task=None, context={}
    --------------------------------------------------------------------------------
    asgi start: task='Task-5', context={}
    --------------------------------------------------------------------------------
    asgi end: task='Task-5', context={'cvar3': True, 'cvar1': True, 'cvar2': True}
    --------------------------------------------------------------------------------
    connection_lost: task=None, context={}
    ---------------------------------------

    The context of connection_lost is empty which suggests that this bug is not present in windows implementation, still I'll double check it once I add tests on the PR.

  7. pmeier commented on Dec 14, 2025

    @pmeier
    pmeier
    on Dec 14, 2025
    Author
    More actions

    Both the bug report as well as the regression tests in uvicorn also suggest that Windows is not affected.

  8. kumaraditya303
    added a commit that references this issue on Mar 21, 2026

    gh-140947: fix contextvars handling for server tasks in asyncio (#141158

    60fbc20
  9. kumaraditya303 commented on Mar 21, 2026

    @kumaraditya303
    kumaraditya303
    on Mar 21, 2026
    Contributor
    More actions

    The fix is merged in main branch, I'll prefer to wait for some time before backporting this to older branch just to makes sure that if any bug pops up it only affects main branch.

  10. Kludex commented on Mar 21, 2026

    @Kludex
    Kludex
    on Mar 21, 2026
    More actions

    Which older branch?

  11. kumaraditya303 commented on Mar 21, 2026

    @kumaraditya303
    kumaraditya303
    on Mar 21, 2026
    Contributor
    More actions

    3.14 and 3.13 branches

  12. Kludex commented on Mar 21, 2026

    @Kludex
    Kludex
    on Mar 21, 2026
    More actions

    Makes sense. Thanks for the PR. :)

  13. kumaraditya303
    moved this from Todo to Done in asyncioon Mar 21, 2026
  14. CuriousLearner
    added a commit that references this issue on Mar 22, 2026

    Merge remote-tracking branch 'upstream/main' into fix-pythongh-138577

    2691ad9
  15. Kludex
    added a commit that references this issue on Apr 21, 2026

    Add `--reset-contextvars` flag to isolate ASGI request context

    7875420
  16. ljfp
    added a commit that references this issue on Apr 25, 2026

    pythongh-140947: fix contextvars handling for server tasks in asyncio (…

    97e41ae
  17. kumaraditya303
    removed
    3.13only security fixesonly security fixes
    3.14bugs and security fixesbugs and security fixes
    on Jul 23, 2026
  18. kumaraditya303 commented on Jul 23, 2026

    @kumaraditya303
    kumaraditya303
    on Jul 23, 2026
    Contributor
    More actions

    After much thought I think backporting this is too risky as it a large behavior change of old code. Keeping this for 3.15+.

    Reacted by Marcelo Trylesinski
  19. kumaraditya303
    closed this as completedon Jul 23, 2026
  20. s0riano
    added 2 commits that reference this issue on Sep 29, 2026

    Resume reads in the connection's context

    e44756f

    Resume reads in the connection's context

    da65b06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @kumaraditya303
    kumaraditya303

Labels

3.15bugs and security fixesbugs and security fixestopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Projects

  • asyncio
    Status
    Done
    Show more project fields

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.