python-trio / trio-asyncio

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

Manually calling loop.stop causes deadlock #58

Closed tjstum closed 5 years ago

tjstum commented 5 years ago

Apologies if this is just my fault. Feel free to Won't Fix it if so 😄 I'm toying around with trio-asyncio and had a hard time figuring out what went wrong here.

If you have a toy program like such

import trio
import trio_asyncio

async def setAfterWait(event):
    await trio.sleep(5)
    print('setting')
    event.set()

async def asyncio_loop(event):
    async with trio_asyncio.open_loop() as loop:
        print('in')
        await event.wait()
        loop.stop()
        print('stopped')
    print('dedented')

async def main():
    event = trio.Event()
    async with trio.open_nursery() as nursery:
        nursery.start_soon(setAfterWait, event)
        nursery.start_soon(serve, event)

And you run this, you get print out like:

in
<5 second pause>
setting
stopped

And then the program hangs forever (you can KI it, which is always nice!). However, if you remove the explicit call to loop.stop(), it works as you expect.

in
<5 seconds>
setting
stopped
dedented

I ran into this while trying to debug a more complicated program where a loop.stop() had been added in a misguided attempt to debug a different shutdown issue (forgetting to cancel a nursery that had a bunch of "background" tasks).

@oremanj helped me get a task tree dump, and it looks like trio-asyncio gets stuck here:

[    4] |   |-- task __main__.asyncio_loop:
[    5] |   |   asyncio_loop at __mp_main__:14
[    6] |   |   _AsyncGeneratorContextManager.__aexit__ at async_generator._util:42
[    7] |   |   step at async_generator._impl:366
[    8] |   |   nursery nursery in open_loop at trio_asyncio.async_:108
[     ] |   |   +-- nested child:
[    9] |   |       open_loop at trio_asyncio.async_:123
[   10] |   |       Event.wait at trio._sync:73
[   11] |   |       ParkingLot.park at trio._core._parking_lot:137
[   12] |   |       wait_task_rescheduled at trio._core._traps:166

I'm guessing that what happens is that the __aexit__ is submitting a callback (TrioEventLoop._queue_handle) after the loop has been stopped, so it never actually fires. I definitely messed this up, but I figured I'd write this up in case anyone else runs into it. If there's a reasonable way to fix/document better, I'd be happy to pitch in.

Thanks!

smurfix commented 5 years ago

You're right – calling loop.stop yourself is a bad idea in conjunction with trio-asyncio. I'd appreciate a PR with some text that warns against doing that kind of thing.

smurfix commented 5 years ago

Merged into "next" branch.