Skip to content

Commit 723a7f7

Browse files
authored
Merge pull request #96 from oremanj/fix-close-order
Cancel trio_as_aio tasks before stopping the trio-asyncio loop
2 parents 39f79e3 + 6e5f468 commit 723a7f7

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

newsfragments/89.bugfix.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
trio-asyncio now cancels any Trio tasks that were started inside a trio-asyncio
2+
loop (using e.g. :func:`trio_as_aio`) before it allows the trio-asyncio loop
3+
to close. This should resolve some cases of deadlocks and "RuntimeError: Event loop
4+
is closed" when an ``async with open_loop():`` block is cancelled.

trio_asyncio/_loop.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,20 +391,43 @@ async def async_main(*args):
391391
"""
392392

393393
# TODO: make sure that there is no asyncio loop already running
394-
async with trio.open_nursery() as nursery:
394+
395+
# The trio-asyncio loop can't shut down until all trio_as_aio tasks
396+
# (or others using run_trio) have exited. This is because the
397+
# termination of such a Trio task sets an asyncio future, which
398+
# uses call_soon(), which won't work if the loop is closed.
399+
# So, we use two nested nurseries.
400+
async with trio.open_nursery() as loop_nursery:
395401
loop = TrioEventLoop(queue_len=queue_len)
396402
old_loop = current_loop.set(loop)
397403
try:
398404
loop._closed = False
399-
await loop._main_loop_init(nursery)
400-
await nursery.start(loop._main_loop)
401-
yield loop
405+
async with trio.open_nursery() as tasks_nursery:
406+
await loop._main_loop_init(tasks_nursery)
407+
await loop_nursery.start(loop._main_loop)
408+
yield loop
409+
tasks_nursery.cancel_scope.cancel()
410+
411+
# Allow all submitted run_trio() tasks calls a chance
412+
# to start before the tasks_nursery closes, unless the
413+
# loop stops (due to someone else calling stop())
414+
# before that:
415+
async with trio.open_nursery() as sync_nursery:
416+
sync_nursery.cancel_scope.shield = True
417+
418+
@sync_nursery.start_soon
419+
async def wait_for_sync():
420+
if not loop.is_closed():
421+
await loop.synchronize()
422+
sync_nursery.cancel_scope.cancel()
423+
424+
await loop.wait_stopped()
425+
sync_nursery.cancel_scope.cancel()
402426
finally:
403427
try:
404428
await loop._main_loop_exit()
405429
finally:
406430
loop.close()
407-
nursery.cancel_scope.cancel()
408431
current_loop.reset(old_loop)
409432

410433

0 commit comments

Comments
 (0)