python-trio / trio-asyncio

a re-implementation of the asyncio mainloop on top of Trio

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

trio_asyncio.open_loop() Causes an error when you call it again

ll2pakll opened this issue · comments

hi, I'm trying to set up a trio to run asyncio functions using the trio-asyncio module.
The instructions here show a variant using wrapper.
https://trio-asyncio.readthedocs.io/en/latest/usage.html#cross-calling
This is the option I'm trying to use because it gives me the option to use trio.run. It works fine when I run a function using wrapper the first time. But after the second run I always get this error

RuntimeError: Task <Task pending name='Task-3' coro=<_call_defer() running at C:\Users\user\miniconda3\envs\stylegan3\lib\site-packages\trio_asyncio\_adapter.py:17> cb=[run_aio_future.<locals>. done_cb() at C:\Users\user\miniconda3\envs\stylegan3\lib\site-packages\trio_asyncio\_util.py:29]> got Future <Future pending cb=[Protocol._on_waiter_completed()]> attached to a different loop

Apparently, trio_asyncio.open_loop() can only be run once, and then somehow already refer to one already running, or something like that. The instructions don't say a word about this. Maybe you can tell me what to do? Here's the function I'm using

    async def asyncio_start(self, funk, *args, **kwargs):

        p_o = partial(funk, *args, **kwargs)
        result = await self.nursery.start(self.async_main_wrapper, p_o)
        print(result)
        result = await self.nursery.start(self.async_main_wrapper, p_o)
        print(result)
        return result

    async def async_main_wrapper(self, p_o, task_status=trio.TASK_STATUS_IGNORED):

        async with trio_asyncio.open_loop() as self.loop:
            assert self.loop == asyncio.get_event_loop()
            result = await p_o()
            task_status.started(result)

Your example is not reproducible - I can't run it because it's missing part of the code. I tried to fill in the blanks and I got something that worked fine:

$ cat t.py
import trio
import asyncio
import trio_asyncio
from functools import partial

@trio_asyncio.aio_as_trio
async def aio_sleep_and_return(duration):
    await asyncio.sleep(duration)
    return (duration, id(asyncio.get_event_loop()))

async def async_main_wrapper(p_o, task_status=trio.TASK_STATUS_IGNORED):
    async with trio_asyncio.open_loop() as loop:
        assert loop == asyncio.get_event_loop()
        result = await p_o()
        task_status.started(result)

async def main():
    async with trio.open_nursery() as nursery:
        p_o = partial(aio_sleep_and_return, 1)
        result = await nursery.start(async_main_wrapper, p_o)
        print(result)
        result = await nursery.start(async_main_wrapper, p_o)
        print(result)
        return result

if __name__ == "__main__":
    trio.run(main)

$ python3.10 t.py
(1, 4313919936)
(1, 4313916864)

Apparently, trio_asyncio.open_loop() can only be run once, and then somehow already refer to one already running, or something like that.

This is false; it works totally fine to have multiple trio-asyncio loops in the same trio program, as the above example demonstrates. However, much asyncio code in practice assumes there is only one loop and doesn't pay careful attention to the ambient loop. If you have multiple loops, asyncio.get_running_loop() and asyncio.get_event_loop() will both return the one you're lexically inside; this is carried in a contextvar so it is also inherited across nursery.start()/start_soon(). Thus, you can get into trouble if you create an object in one context, its constructor captures the current loop, and then you try to use it in a different context. Some of the builtin asyncio objects worked this way in earlier versions of Python but they were cleaned up in 3.10 to instead capture the loop on first use. What Python version are you running?