Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to gracefully terminate an asyncio script with Ctrl-C?

I've read every post I could find about how to gracefully handle a script with an asyncio event loop getting terminated with Ctrl-C, and I haven't been able to get any of them to work without printing one or more tracebacks as I do so. The answers are pretty much all over the place, and I haven't been able implement any of them into this small script:

import asyncio
import datetime
import functools
import signal


async def display_date(loop):
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)


def stopper(signame, loop):
    print("Got %s, stopping..." % signame)
    loop.stop()


loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame), functools.partial(stopper, signame, loop))

loop.run_until_complete(display_date(loop))
loop.close()

What I want to happen is for the script to exit without printing any tracebacks following a Ctrl-C (or SIGTERM/SIGINT sent via kill). This code prints RuntimeError: Event loop stopped before Future completed. In the MANY other forms I've tried based on previous answers, I've gotten a plethora of other types of exception classes and error messages with no idea how to fix them. The code above is minimal right now, but some of the attempts I made earlier were anything but, and none of them were correct.

If you're able to modify the script so that it terminates gracefully, an explanation of why your way of doing it is the right way would be greatly appreciated.

like image 310
JK Laiho Avatar asked Feb 01 '18 12:02

JK Laiho


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.

What is Asyncio sleep ()?

The asyncio. sleep() method suspends the execution of a coroutine. Coroutines voluntarily yield CPU leading to co-operative multitasking through the await keyword.


2 Answers

Use signal handlers:

import asyncio from signal import SIGINT, SIGTERM  async def main_coro():     try:         await awaitable()     except asyncio.CancelledError:         do_cleanup()  if __name__ == "__main__":     loop = asyncio.get_event_loop()     main_task = asyncio.ensure_future(main_coro())     for signal in [SIGINT, SIGTERM]:         loop.add_signal_handler(signal, main_task.cancel)     try:         loop.run_until_complete(main_task)     finally:         loop.close() 
like image 176
ManuelSchneid3r Avatar answered Sep 20 '22 08:09

ManuelSchneid3r


Stopping the event loop while it is running will never be valid.

Here, you need to catch the Ctrl-C, to indicate to Python that you wish to handle it yourself instead of displaying the default stacktrace. This can be done with a classic try/except:

coro = display_date(loop) try:     loop.run_until_complete(coro) except KeyboardInterrupt:     print("Received exit, exiting") 

And, for your use-case, that's it! For a more real-life program, you would probably need to cleanup some resources. See also Graceful shutdown of asyncio coroutines

like image 31
Arthur Avatar answered Sep 22 '22 08:09

Arthur