Skip to content
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

What to do about calls to get_event_loop() in Trio context when no loop is active? #68

Open
oremanj opened this issue Jan 11, 2020 · 6 comments

Comments

@oremanj
Copy link
Member

oremanj commented Jan 11, 2020

Many asyncio objects (such as asyncio.Lock) call asyncio.get_event_loop() in their constructor, if no loop was passed. Much asyncio-using code seems to assume that such objects can be created when no loop is running, and that they'll attach themselves to the loop that winds up running later.

This pattern works fine (at least post #66) outside of Trio context. get_event_loop() will delegate to the installed event loop policy; if no custom policy has been installed, it will return the event loop for the current thread, creating it as a SyncTrioEventLoop if necessary.

Inside Trio context, though, we currently expect every event loop to be a TrioEventLoop bound to an async with open_loop() block somewhere. If no TrioEventLoop has yet been created, what can we do?

lock = asyncio.Lock()
async with trio_asyncio.open_loop():
    [use lock here]

which seems like it might still break some use cases.

  • Possible change: create a new TrioEventLoop, set the current_loop cvar to use it, and teach open_loop() to manage the existing ambient loop instead of creating a new one if an existing ambient loop exists and isn't running. (It should still be possible to nest open_loop() calls to get two different loops with different scoping behavior if the outer loop is running.) This seems like it may have the closest behavior to non-trio-enabled asyncio: you can still set things up against a loop when it's not running, but your callbacks won't, like, run.
  • Possible change: create a new TrioEventLoop and start running it in a system task. I think this is strictly inferior to the previous bullet but it was the first thing I thought of so I mention it for completeness, in case there's some reason I'm missing why it would be better. The hazard is that in the above example, Lock would be in the global loop which is not the same loop as everything inside the async with block. And while I can't immediately think of a reason this wouldn't work, if it breaks it's going to be really confusing to understand.
@smurfix
Copy link
Collaborator

smurfix commented Jan 12, 2020

Third possible solution: create a dummy event loop object with a __getattr__ that delegates everything to the currently-running TrioEventLoop, or raises an exception if that doesn't exist.
That being said I'd sort this possibility between your two, in order of decreasing desirability.

@gc-ss
Copy link

gc-ss commented May 11, 2021

Would it be fair to say that in this library, this line too falls in this trap:

https://github.com/vmagamedov/grpclib/blob/a290e4138ef743396a4d08da3f9fba5f1ba6af0a/grpclib/client.py#L641

@smurfix
Copy link
Collaborator

smurfix commented May 11, 2021

Mostly, yes. Caching the current event loop is a bad idea these days.

@gc-ss
Copy link

gc-ss commented May 11, 2021

Thank you @smurfix

I am currently using python-trio to interact with asyncio based grpclib (the details of it really is that I use betterproto and that in turn uses grpclib https://github.com/danielgtaylor/python-betterproto/blob/02e41afd09f0050a10fea764b15279b82cdd6e6b/src/betterproto/__init__.py#L31)

Since there's a lot of asyncio code out there - I was curious about those asyncio code that call get_event_loop() from __init__

Is there a pattern we can come up with to handle this case reliably (short of putting a patch to the project that works with trio)?

@smurfix
Copy link
Collaborator

smurfix commented May 11, 2021

The way to handle this pattern reliably is to convince upstream not to use it in the first place.

Yes, trio-asyncio could possibly add a workaround (or two or three …) for it, but (a) none of them reliably work in more complicated scenarios, (b) there are other use cases where this pattern goes splat.

Example for (a): consider two libraries where both A and B set up a trio-asyncio loop. A starts its loop and calls B, which creates one of those objects (which stores a ref to A's loop), then starts its own separate trio-asyncio loop, making things go haywire because freely mixing calls to two different asyncio loops simply does not work.

Example for (b): you might want to set up your objects in your main thread's sync code, but then you run the actual asyncio loop in a subthread while the main thread handles your GUI.

@gc-ss
Copy link

gc-ss commented May 12, 2021

@oremanj shared some more thoughts here: https://gitter.im/python-trio/general?at=609bea20012fc62dd5b44c97

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants