Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python asyncio program won't exit

I have an asyncio/Python program with two asyncio tasks:

  • one that crashes
  • one that goes on for ever.

I want my entire program to exit after the first crash. I cannot get it to happen.

import asyncio
import time

def infinite_while():
    while True:
        time.sleep(1)


async def task_1():
    await asyncio.sleep(1)
    assert False


async def task_2():
    loop = asyncio.get_event_loop()
    await loop.run_in_executor(None, lambda: infinite_while())


loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)

tasks = asyncio.gather(task_2(), task_1())
try:
    loop.run_until_complete(tasks)
except (Exception, KeyboardInterrupt) as e:
    print('ERROR', str(e))
    exit()

It prints ERROR but does not exit. When manually closed, the program prints the following stack trace:

Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 39, in _python_exit
    t.join()
  File "/usr/lib/python3.5/threading.py", line 1054, in join
    self._wait_for_tstate_lock()
  File "/usr/lib/python3.5/threading.py", line 1070, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt
like image 355
MrJ Avatar asked Sep 06 '16 14:09

MrJ


People also ask

How do I stop Asyncio from running?

Run an asyncio Event Loop run_until_complete(<some Future object>) – this function runs a given Future object, usually a coroutine defined by the async / await pattern, until it's complete. run_forever() – this function runs the loop forever. stop() – the stop function stops a running loop.

Does Asyncio block sleep?

sleep in asyncio coroutines unless you want to block the main thread, therefore freezing the event loop and probably the whole application as well.

How many times should Asyncio run () be called?

How many times should Asyncio run () be called? It should be used as a main entry point for asyncio programs, and should ideally only be called once. New in version 3.7.


1 Answers

When an exception is risen in a task, it never propagates to the scope where the task was launched via eventloop, i.e. the loop.run_until_complete(tasks) call. Think of it, as if the exception is thrown only in the context of your task, and that is the top level scope where you have the chance to handle it, otherwise it will be risen in the "background".

This said, you will never catch an Exception from the task with this:

try:
    loop.run_until_complete(tasks)
except (Exception, KeyboardInterrupt) as e:
    print('ERROR', str(e))
    exit()

...and that is just how the event loop works. Imagine if you would have a service with several tasks, and one of them would fail, that would stop the whole service.

What you could do is stop the eventloop manually when you catch an exception in task1, e.g.

async def task_1():
    await asyncio.sleep(1)
    try:
        assert False
    except Exception:
        # get the eventloop reference somehow
        eventloop.stop()

However, this is very dirty and kind of hacky, so I would rather suggest to go with the solution that @D-Von suggested, which is much cleaner and safer.

like image 57
bagrat Avatar answered Oct 13 '22 10:10

bagrat