-
-
Notifications
You must be signed in to change notification settings - Fork 344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Why can't you await __aenter__? (Understanding trio's contracts around async context managers) #3033
Comments
For line number debugging purposes, which exact version of Trio are you using? For example, on my machine, I can do this to get the version: >>> import trio
>>> trio.__version__
'0.26.0' |
This comment has been minimized.
This comment has been minimized.
0.22.2 |
I've got this based off your minimal example: from __future__ import annotations
import trio
from contextlib import asynccontextmanager, AbstractAsyncContextManager
from collections.abc import Callable, AsyncGenerator
from typing import TypeVar
T = TypeVar("T")
@asynccontextmanager
async def par_w(a: Callable[[], AbstractAsyncContextManager[T]]) -> AsyncGenerator[T, None]:
ca = a()
value = await ca.__aenter__()
yield value
await ca.__aexit__(None, None, None)
async def run() -> None:
context_manager = trio.open_nursery
async def do_three() -> None:
print("3 start")
await trio.sleep(1)
print("3 end")
async with par_w(context_manager) as nursery:
print("1")
await trio.sleep(1)
print("2")
nursery.start_soon(do_three)
print("end")
print("4")
if __name__ == "__main__":
trio.run(run) and I don't see any errors happening, so I'm guessing it's an issue with whatever async context manager you are passing in as |
The issue here is similar to the async generator issue. Because you're calling with statement methods directly, you have to be incredibly careful to preserve the nesting order. Otherwise, Trio's internal cancel scope stack can get messed up, breaking everything. It's probably best to avoid calling them directly, instead use |
Say we have context managers:
We want to parallelize their enter/exit.
One approach: create a new context manager c that duplicates the logic from both, but ideally we don't have to do that—keep both a and b as reusable context managers, but also run them in parallel.
Instead, let's create a combinator:
What's the right way to implement
par_with
?First idea that came to mind:
But this causes nested nurseries to get canceled as soon as the enters finish. OK, so far this makes sense to me.
So we want to keep the outer nursery strictly surrounding both.
Another stab:
The above doesn't work and triggers:
(Sometimes it raises GeneratorExit instead.)
Rather than dig into that, here is a simpler version that just deals with a single context manager instead of two:
It raises the same:
(Sometimes it also raises GeneratorExit instead.)
In the end, I thought of an entirely different approach—launch each context manager in its own task and synchronize with the original context manager to yield—but I was curious to understand why the above doesn't work. What trio contract is being violated?
The text was updated successfully, but these errors were encountered: