Repository navigation
context variables can leak out of asyncio.Task #140947
Copy link
Copy link
Closed
Labels
3.15bugs and security fixesbugs and security fixestopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
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:
cvar1is set at the beginning of the function, which may or may not be before the reading is paused.cvar2is set after the reading is paused.cvar3is 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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
3.15bugs and security fixesbugs and security fixestopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Projects
- StatusShow more project fieldsDone
Activity
kumaraditya303 commentedon Dec 14, 2025
Output on Windows:
The context of
connection_lostis 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.pmeier commentedon Dec 14, 2025
Both the bug report as well as the regression tests in uvicorn also suggest that Windows is not affected.
gh-140947: fix contextvars handling for server tasks in asyncio (#141158
kumaraditya303 commentedon Mar 21, 2026
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.
Kludex commentedon Mar 21, 2026
Which older branch?
kumaraditya303 commentedon Mar 21, 2026
3.14 and 3.13 branches
Kludex commentedon Mar 21, 2026
Makes sense. Thanks for the PR. :)
Merge remote-tracking branch 'upstream/main' into fix-pythongh-138577
Add `--reset-contextvars` flag to isolate ASGI request context
pythongh-140947: fix contextvars handling for server tasks in asyncio (…
kumaraditya303 commentedon Jul 23, 2026
After much thought I think backporting this is too risky as it a large behavior change of old code. Keeping this for 3.15+.
Resume reads in the connection's context
Resume reads in the connection's context