python-trio / trio-asyncio

a re-implementation of the asyncio mainloop on top of Trio
Other
188 stars 38 forks source link

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

Open oremanj opened 4 years ago

oremanj commented 4 years ago

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?

smurfix commented 4 years ago

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 commented 3 years ago

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 commented 3 years ago

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

gc-ss commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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