22
votes

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?

3
I have found an answer to this on the python-tulip list: groups.google.com/d/msg/python-tulip/Jce-VVXJZvk/o-g74rtuIHkJJinnog
if you think you found the solution (schedule the coroutine on signal using asyncio.async instead of calling it as a signal handler directly); you could post it as your own answer and accept it so that others could see that the question is answered. Unrelated: you might be insulting some volunteers on this site by suggesting that the motive for them to help you is fictional internet points.jfs

3 Answers

17
votes

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)))
5
votes
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.

2
votes

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