python-trio / trio-asyncio

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

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

Closed ll2pakll closed 5 months ago

ll2pakll commented 1 year ago

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)
oremanj commented 7 months ago

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?