Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

asyncio - How can coroutines be used in signal handlers?

I am developing an application that uses asyncio from python3.4 for networking. When this application shuts down cleanly, a node needs to "disconnect" from the hub. This disconnect is an active process that requires a network connection so the loop needs to wait for this to complete before shutting down.

My issue is that using a coroutine as a signal handler will result in the application not shutting down. Please consider the following example:

import asyncio
import functools
import os
import signal

@asyncio.coroutine
def ask_exit(signame):
    print("got signal %s: exit" % signame)
    yield from asyncio.sleep(10.0)
    loop.stop()

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

print("Event loop running forever, press CTRL+c to interrupt.")
print("pid %s: send SIGINT or SIGTERM to exit." % os.getpid())
loop.run_forever()

If you run this example and then press Ctrl+C, nothing will happen. The question is, how do I make this behavior happen with siganls and coroutines?

like image 521
Jinnog Avatar asked Apr 26 '14 16:04

Jinnog


4 Answers

Syntax for python >=3.5

loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame),
                            lambda: asyncio.ensure_future(ask_exit(signame)))
like image 109
svs Avatar answered Oct 01 '22 20:10

svs


loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame),
                            asyncio.async, ask_exit(signame))

That way the signal causes your ask_exit to get scheduled in a task.

like image 43
Jason Fried Avatar answered Oct 01 '22 19:10

Jason Fried


python3.8

  • 1st attempt: used async def handler_shutdown, and wrapped it in loop.create_task() when passing to add_signal_handler()
  • 2nd attempt: don't use async for def handler_shutdown().
  • 3rd attempt: wrap handler_shutdown and param in functools.partial()

e.g.

import asyncio
import functools
def handler_shutdown(signal, loop, tasks, http_runner, ):
    ...
    ...
def main():
    loop = asyncio.get_event_loop()
    for signame in ('SIGINT', 'SIGTERM', 'SIGQUIT'):
                print(f"add signal handler {signame} ...")
                loop.add_signal_handler(
                    getattr(signal, signame),
                    functools.partial(handler_shutdown,
                            signal=signame, loop=loop, tasks=tasks,
                            http_runner=http_runner
                            )
                    )
  • The main issue i had was error

    raise TypeError("coroutines cannot be used "

  • solved it by wrapping the routine in loop.create_task()

  • then solved it by removing async form signal handler function
  • for named param to handler also use functools.partial
like image 28
Pieter Avatar answered Oct 01 '22 18:10

Pieter


Syntax for python >=3.7

loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame),
                            lambda signame=signame: asyncio.create_task(ask_exit(signame)))

Note

This is basically same as @svs's answers with two differences:

  1. Usage of the more recent Python 3.7+ method asyncio.create_task which is "more readable" than asyncio.ensure_future.
  2. Binding signame immediately to the lambda function avoids the problem of late binding leading to the expected-unexpected™ behavior referred to in the comment by @R2RT. This was shamelessly copied from Lynn Root's blog post: Graceful Shutdowns with asyncio (read the whole series to learn more about asyncio's beautiful goriness).
like image 41
pogojotz Avatar answered Oct 01 '22 19:10

pogojotz