Description
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?
- Old behavior (pre Bring trio-asyncio into the next decade #66): throw an exception from
get_event_loop()
. This caused problems for a user on gitter today: https://gitter.im/python-trio/general?at=5e1a40f2b720fa5b3cf9dbc4 - New behavior (post Bring trio-asyncio into the next decade #66): return None from
get_event_loop()
. This would have fixed the user's issue, but is confusing because it raises an error later (when someone tries to use the loop) without indicating why they got None.get_event_loop()
for the default asyncio event loop policy never returns None. - Possible change: return a dummy event loop that raises when you try to use it. This would also fix the user's issue, and is better than returning None, but doesn't allow for patterns like
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 theasync 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.