Skip to content

Cancel trio_as_aio tasks before stopping the trio-asyncio loop #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 6, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions newsfragments/89.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
trio-asyncio now cancels any Trio tasks that were started inside a trio-asyncio
loop (using e.g. :func:`trio_as_aio`) before it allows the trio-asyncio loop
to close. This should resolve some cases of deadlocks and "RuntimeError: Event loop
is closed" when an ``async with open_loop():`` block is cancelled.
33 changes: 28 additions & 5 deletions trio_asyncio/_loop.py
Original file line number Diff line number Diff line change
@@ -391,20 +391,43 @@ async def async_main(*args):
"""

# TODO: make sure that there is no asyncio loop already running
async with trio.open_nursery() as nursery:

# The trio-asyncio loop can't shut down until all trio_as_aio tasks
# (or others using run_trio) have exited. This is because the
# termination of such a Trio task sets an asyncio future, which
# uses call_soon(), which won't work if the loop is closed.
# So, we use two nested nurseries.
async with trio.open_nursery() as loop_nursery:
loop = TrioEventLoop(queue_len=queue_len)
old_loop = current_loop.set(loop)
try:
loop._closed = False
await loop._main_loop_init(nursery)
await nursery.start(loop._main_loop)
yield loop
async with trio.open_nursery() as tasks_nursery:
await loop._main_loop_init(tasks_nursery)
await loop_nursery.start(loop._main_loop)
yield loop
tasks_nursery.cancel_scope.cancel()

# Allow all submitted run_trio() tasks calls a chance
# to start before the tasks_nursery closes, unless the
# loop stops (due to someone else calling stop())
# before that:
async with trio.open_nursery() as sync_nursery:
sync_nursery.cancel_scope.shield = True

@sync_nursery.start_soon
async def wait_for_sync():
if not loop.is_closed():
await loop.synchronize()
sync_nursery.cancel_scope.cancel()

await loop.wait_stopped()
sync_nursery.cancel_scope.cancel()
finally:
try:
await loop._main_loop_exit()
finally:
loop.close()
nursery.cancel_scope.cancel()
current_loop.reset(old_loop)