Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Indirectly stopping a python asyncio event loop through SIGTERM has no effect

The following minimal program reproduces the problem.

import asyncio
import signal

class A:
    def __init__(self):
        self._event_loop = asyncio.new_event_loop()

    def run(self):
        print('starting event loop')
        self._event_loop.run_forever()
        print('event loop has stopped')

    def stop(self):
        print('stopping event loop')
        self._event_loop.stop()


if __name__ == '__main__':
    a = A()

    def handle_term(*args):
        a.stop()

    signal.signal(signal.SIGTERM, handle_term)
    a.run()

If you run the program and send a SIGTERM to the process, the print statement in line 16 (stopping event loop) is called but the programm does not terminate and the print statement in line 13 (event loop has stopped) is never called. So it seems that the event loop is never stopped and self._event_loop.run_forever() blocks indefinitely.

Why is this?

Note: A modified version of the program, where a.stop() is not called by a signal handler but by a seperate thread with a delay, works as expected. How can it make a difference how a.stop() is called?

like image 812
Max Tet Avatar asked Mar 06 '17 14:03

Max Tet


1 Answers

Instead of signal.signal() use loop.add_signal_handler():

import asyncio
import signal

import os


class A:
    def __init__(self):
        self.loop = asyncio.new_event_loop()
        self.loop.add_signal_handler(signal.SIGTERM, self.stop)

    def stop(self):
        print('stopping')
        self.loop.stop()

    def run(self, close=True):
        print('starting loop')
        try:
            self.loop.run_forever()
            print('loop stopped')
        finally:
            if close:
                self.loop.close()


if __name__ == '__main__':
    print("to stop run:\nkill -TERM {}".format(os.getpid()))
    a = A()
    a.run()
like image 161
Udi Avatar answered Nov 01 '22 15:11

Udi