Created
July 27, 2016 13:34
-
-
Save nvgoldin/30cea3c04ee0796ebd0489aa62bcf00a to your computer and use it in GitHub Desktop.
Python 3.5 asyncio - shutdown all tasks safely using signal handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import signal | |
import functools | |
async def looping_task(loop, task_num): | |
try: | |
while True: | |
print('{0}:in looping_task'.format(task_num)) | |
await asyncio.sleep(5.0, loop=loop) | |
except asyncio.CancelledError: | |
return "{0}: I was cancelled!".format(task_num) | |
async def shutdown(sig, loop): | |
print('caught {0}'.format(sig.name)) | |
tasks = [task for task in asyncio.Task.all_tasks() if task is not | |
asyncio.tasks.Task.current_task()] | |
list(map(lambda task: task.cancel(), tasks)) | |
results = await asyncio.gather(*tasks, return_exceptions=True) | |
print('finished awaiting cancelled tasks, results: {0}'.format(results)) | |
loop.stop() | |
loop = asyncio.get_event_loop() | |
for i in range(5): | |
asyncio.ensure_future(looping_task(loop, i), loop=loop) | |
loop.add_signal_handler(signal.SIGTERM, | |
functools.partial(asyncio.ensure_future, | |
shutdown(signal.SIGTERM, loop))) | |
try: | |
loop.run_forever() | |
finally: | |
loop.close() | |
Something like this works for me in 3.10
. I like nvgoldin's functional style but i find it clunky to read in python and with python-black it's one less line to just do it the imperative way
def add_signal_handlers():
loop = asyncio.get_event_loop()
async def shutdown(sig: signal.Signals) -> None:
"""
Cancel all running async tasks (other than this one) when called.
By catching asyncio.CancelledError, any running task can perform
any necessary cleanup when it's cancelled.
"""
tasks = []
for task in asyncio.all_tasks(loop):
if task is not asyncio.current_task(loop):
task.cancel()
tasks.append(task)
results = await asyncio.gather(*tasks, return_exceptions=True)
print("Finished awaiting cancelled tasks, results: {0}".format(results))
loop.stop()
for sig in [signal.SIGINT, signal.SIGTERM]:
loop.add_signal_handler(sig, lambda: asyncio.create_task(shutdown(sig)))
I got this working for SIGINT, but I had that working with much simpler code that just did:
def signal_handler(sig, frame):
global group
group.cancel()
async def main():
global group
group = asyncio.gather(
task1(),
task2(),
)
await group
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
asyncio.run(main())
except asyncio.exceptions.CancelledError:
pass
SIGTERM never worked for that, but it isn't working with this method either! SIGTERM never enters my signal handler.
My code is a bit of a hybrid of the two examples above, as much of the original doesn't work under python 3.12:
async def signal_handler(sig, loop):
"""
Exit cleanly on SIGTERM ("docker stop"), SIGINT (^C when interactive)
"""
print('caught {0}'.format(sig.name))
tasks = [task for task in asyncio.all_tasks() if task is not
asyncio.current_task()]
list(map(lambda task: task.cancel(), tasks))
results = await asyncio.gather(*tasks, return_exceptions=True)
print('finished awaiting cancelled tasks, results: {0}'.format(results))
loop.stop()
if __name__ == "__main__":
loop = asyncio.new_event_loop()
asyncio.ensure_future(task1(), loop=loop)
asyncio.ensure_future(task2(), loop=loop)
loop.add_signal_handler(signal.SIGTERM,
functools.partial(asyncio.ensure_future,
signal_handler(signal.SIGTERM, loop)))
loop.add_signal_handler(signal.SIGINT,
functools.partial(asyncio.ensure_future,
signal_handler(signal.SIGINT, loop)))
try:
loop.run_forever()
finally:
loop.close()
task1
can terminate immediately, but task2
has cleanup code that is clearly being executed after SIGINT, but not after SIGTERM
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@rjjanuary Ever figure it out? Exact same thing for me.