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.
importasyncioimportcontextvarsimportsyscvar1=contextvars.ContextVar("cvar1")
cvar2=contextvars.ContextVar("cvar2")
cvar3=contextvars.ContextVar("cvar3")
defprint_diagnostics(label):
task=t.get_name() if (t:=asyncio.current_task()) elseNonecontext= {c.name: vforc, vincontextvars.copy_context().items()}
print(f"{label}: {task=}, {context=}\n{'-'*80}")
classDemoProtocol(asyncio.Protocol):
def__init__(self, on_conn_lost):
self.transport=Noneself.on_conn_lost=on_conn_lostself.tasks=set()
defconnection_made(self, transport):
print_diagnostics("connection_made")
self.transport=transportdefdata_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()
defconnection_lost(self, exc):
print_diagnostics("connection_lost")
ifnotself.on_conn_lost.done():
self.on_conn_lost.set_result(True)
asyncdefasgi(self):
print_diagnostics("asgi start")
cvar1.set(True)
# make sure that we only resume after the pause# otherwise the resume does nothingwhilenotself.transport._paused:
awaitasyncio.sleep(0.1)
cvar2.set(True)
self.transport.resume_reading()
cvar3.set(True)
print_diagnostics("asgi end")
asyncdefmain():
print(f"Python: {sys.version}\n{'-'*80}")
loop=asyncio.get_running_loop()
on_conn_lost=loop.create_future()
host, port="127.0.0.1", 8888asyncwithawaitloop.create_server(lambda: DemoProtocol(on_conn_lost), host, port):
reader, writer=awaitasyncio.open_connection(host, port)
writer.write(b"anything")
awaitwriter.drain()
writer.close()
awaitwriter.wait_closed()
awaiton_conn_lostif__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:
cvar1 is set at the beginning of the function, which may or may not be before the reading is paused.cvar2 is set after the reading is paused.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
Bug report
Bug description:
This is a minimal reproducer for Kludex/uvicorn#2167. TL;DR when using
asyncioas event loop, users ofuvicornwere seeing polluted contexts after sending in large payloads. For thoseuvicornpaused reading on the main thread and resumed it in a task.The
asgitask 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 (
cvar1andcvar2) leak out of the task into the main thread.CPython versions tested on:
3.14
Operating systems tested on:
Linux
Linked PRs