You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Accept explicit contextvars.Context in asyncio create_task() API #91150
Now asyncio creates a new context copy on task creation.
It is the perfect behavior *by default* and should stay as is.
However, sometimes passing an explicit context into a task and using back the context modified by task code is desired.
The most obvious use-case is testing: both unittest and pytest have multi-phase test initialization (asyncSetUp() methods and async fixtures correspondingly). If asyncSetUp() updates context variable -- test code expects to see this change.
Currently, unittest runs the setup-test-cleanup chain in a single task and uses an internal queue to push a new coroutine for execution and get results back. It works but is cumbersome.
Another solution is to create a task per test execution step and wrap the task creation with Context.run(). The problem is in getting the updated context back. A task creates a context copy on starting, thus the modified context is stored in the task internal attribute only. To get it back a trampoline async function should be used, e.g.
asyncdefwrapper(coro):
try:
returnawaitcorofinally:
context=contextvars.copy_context()
# store the context copy somewhere
Again, it looks more complicated as it should be.
The proposal is:
Add 'context' keyword-only argument to asyncio.create_task() and loop.create_task().
Use this context if explicitly passed, create a copy of the current context otherwise.
The proposal is backward-compatible. Low-level API (call_soon(), call_later() etc.) already accept 'context' argument.
The attached PR demonstrates how the proposed API simplifies unittest.IsolatedAsyncioTestCase internals.
New changeset 9523c0d by Andrew Svetlov in branch 'main': bpo-46994: Accept explicit contextvars.Context in asyncio create_task() API (GH-31837) 9523c0d
@agronholm I think the GIL protects us here. We don't do anything that releases the GIL between patching and unpatching the module, so it's thread safe.
Never mind, I was confused and this is not thread safe.
The proposal is backward-compatible. Low-level API (call_soon(), call_later() etc.) already accept 'context' argument.
Depends on the definition of backwards compatible, I guess? This particular change broke AnyIO's tests with uvloop: agronholm/anyio#439 (not that I'm against this addition, but...).
Activity
asvetlov commentedon Mar 12, 2022
Now asyncio creates a new context copy on task creation.
It is the perfect behavior *by default* and should stay as is.
However, sometimes passing an explicit context into a task and using back the context modified by task code is desired.
The most obvious use-case is testing: both unittest and pytest have multi-phase test initialization (asyncSetUp() methods and async fixtures correspondingly). If asyncSetUp() updates context variable -- test code expects to see this change.
Currently, unittest runs the setup-test-cleanup chain in a single task and uses an internal queue to push a new coroutine for execution and get results back. It works but is cumbersome.
Another solution is to create a task per test execution step and wrap the task creation with Context.run(). The problem is in getting the updated context back. A task creates a context copy on starting, thus the modified context is stored in the task internal attribute only. To get it back a trampoline async function should be used, e.g.
Again, it looks more complicated as it should be.
The proposal is:
The proposal is backward-compatible. Low-level API (call_soon(), call_later() etc.) already accept 'context' argument.
The attached PR demonstrates how the proposed API simplifies unittest.IsolatedAsyncioTestCase internals.
1st1 commentedon Mar 13, 2022
Yeah, +1 to add a parameter. Fwiw it was on my idea list when i was working on the pep
asvetlov commentedon Mar 14, 2022
New changeset 9523c0d by Andrew Svetlov in branch 'main':
bpo-46994: Accept explicit contextvars.Context in asyncio create_task() API (GH-31837)
9523c0d
vstinner commentedon Mar 18, 2022
Nice enhancement!
nzig commentedon May 25, 2022
Any idea how this could be backported to older Python versions? I'm using 3.8. The best I've come up with is
Which is a horrible hack. Any better ideas?
Edit: Added thread safety check.
agronholm commentedon May 25, 2022
Never mind the hackiness, this is outright dangerous. What if there's another event loop running in another thread, doing the same exact thing?
nzig commentedon May 25, 2022
@agronholm I think the GIL protects us here. We don't do anything that releases the GIL between patching and unpatching the module, so it's thread safe.Never mind, I was confused and this is not thread safe.
agronholm commentedon May 25, 2022
The GIL can switch threads any time between byte code instructions. Here's the disassembled byte code for that function of yours:
agronholm commentedon May 25, 2022
Depends on the definition of backwards compatible, I guess? This particular change broke AnyIO's tests with uvloop: agronholm/anyio#439 (not that I'm against this addition, but...).