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?
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)))
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.
python3.8
async def handler_shutdown
, and wrapped it in loop.create_task()
when passing to add_signal_handler()
def handler_shutdown()
.functools.partial()
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()
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)))
This is basically same as @svs's answers with two differences:
asyncio.create_task
which is "more readable" than asyncio.ensure_future
.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).If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With