Stack Overflow for Teams is a private, secure spot for you and your coworkers to find and share information. Learn more

and we daemonize it using start-stop-daemon which by default sends SIGTERM ( TERM ) signal on --stop .

Let's suppose the current step performed is #2 . And at this very moment we're sending TERM signal.

What happens is that the execution terminates immediately.

I've found that I can handle the signal event using signal.signal(signal.SIGTERM, handler) but the thing is that it still interrupts the current execution and passes the control to handler .

So, my question is - is it possible to not interrupt the current execution but handle the TERM signal in a separated thread (?) so that I was able to set shutdown_flag = True so that mainloop() had a chance to stop gracefully?

I did what you are asking for before by using signalfd and masking out the delivery of the SIGTERM to the process. Eric Urban Aug 28 '13 at 22:45 def __init__(self): signal.signal(signal.SIGINT, self.exit_gracefully) signal.signal(signal.SIGTERM, self.exit_gracefully) def exit_gracefully(self,signum, frame): self.kill_now = True if __name__ == '__main__': killer = GracefulKiller() while not killer.kill_now: time.sleep(1) print("doing something in a loop ...") print "End of the program. I was killed gracefully :)" Thanks for the idea! I used a modified approach in reboot-guard. github.com/ryran/reboot-guard/blob/master/rguard#L284:L304 rsaw Sep 6 '15 at 15:28 This is the best answer (no threads required), and should be the preferred first-try approach. jose.angel.jimenez Oct 12 '15 at 16:56 @Mausy5043 Python allows you to not have parenthesis for defining classes. Although it's perfectly fine for python 3.x, but for python 2.x, best practice is to use "class XYZ(object):". Reason being: docs.python.org/2/reference/datamodel.html#newstyle Mayank Jaiswal Dec 24 '15 at 10:27 In worse case, that would simply mean doing another iteration before shuting down gracefully. The False value is set only once, and then it can only go from False to True so multiple access is not an issue. Alceste_ Jul 25 '18 at 15:35

First, I'm not certain that you need a second thread to set the shutdown_flag .
Why not set it directly in the SIGTERM handler?

An alternative is to raise an exception from the SIGTERM handler, which will be propagated up the stack. Assuming you've got proper exception handling (e.g. with with / contextmanager and try: ... finally: blocks) this should be a fairly graceful shutdown, similar to if you were to Ctrl+C your program.

Example program signals-test.py :

#!/usr/bin/python
from time import sleep
import signal
import sys
def sigterm_handler(_signo, _stack_frame):
    # Raises SystemExit(0):
    sys.exit(0)
if sys.argv[1] == "handle_signal":
    signal.signal(signal.SIGTERM, sigterm_handler)
    print "Hello"
    i = 0
    while True:
        i += 1
        print "Iteration #%i" % i
        sleep(1)
finally:
    print "Goodbye"

Now see the Ctrl+C behaviour:

$ ./signals-test.py default
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
^CGoodbye
Traceback (most recent call last):
  File "./signals-test.py", line 21, in <module>
    sleep(1)
KeyboardInterrupt
$ echo $?

This time I send it SIGTERM after 4 iterations with kill $(ps aux | grep signals-test | awk '/python/ {print $2}'):

$ ./signals-test.py default
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
Terminated
$ echo $?

This time I enable my custom SIGTERM handler and send it SIGTERM:

$ ./signals-test.py handle_signal
Hello
Iteration #1
Iteration #2
Iteration #3
Iteration #4
Goodbye
$ echo $?
                "Why not set it directly in the SIGTERM handler" --- because the worker thread would interrupt on a random place. If you put multiple statements into your worker loop you will see that your solution terminates a worker on a random position, which leaves the job in an unknown state.
– zerkms
                Jul 4 '14 at 22:10
                If you just set a flag and not raise exception then it will be the same as with thread. So using thread is superfluous here.
– Suor
                May 26 '15 at 4:31

I think you are near to a possible solution.

Execute mainloop in a separate thread and extend it with the property shutdown_flag. The signal can be caught with signal.signal(signal.SIGTERM, handler) in the main thread (not in a separate thread). The signal handler should set shutdown_flag to True and wait for the thread to end with thread.join()

Threads are not required here. In a single threaded program itself, you can first register a signal handler (registering a signal handler is non blocking) and then write mainloop. Signal handler function should set a flag when and loop should check for this flag. I have pasted a class based solution for the same here. – Mayank Jaiswal Jul 16 '15 at 21:01 signal.signal(signal.SIGINT, handler_stop_signals) signal.signal(signal.SIGTERM, handler_stop_signals) while run: pass # do stuff including other IO stuff

Based on the previous answers, I have created a context manager which protects from sigint and sigterm.

import logging
import signal
import sys
class TerminateProtected:
    """ Protect a piece of code from being killed by SIGINT or SIGTERM.
    It can still be killed by a force kill.
    Example:
        with TerminateProtected():
            run_func_1()
            run_func_2()
    Both functions will be executed even if a sigterm or sigkill has been received.
    killed = False
    def _handler(self, signum, frame):
        logging.error("Received SIGINT or SIGTERM! Finishing this block, then exiting.")
        self.killed = True
    def __enter__(self):
        self.old_sigint = signal.signal(signal.SIGINT, self._handler)
        self.old_sigterm = signal.signal(signal.SIGTERM, self._handler)
    def __exit__(self, type, value, traceback):
        if self.killed:
            sys.exit(0)
        signal.signal(signal.SIGINT, self.old_sigint)
        signal.signal(signal.SIGTERM, self.old_sigterm)
if __name__ == '__main__':
    print("Try pressing ctrl+c while the sleep is running!")
    from time import sleep
    with TerminateProtected():
        sleep(10)
        print("Finished anyway!")
    print("This only prints if there was no sigint or sigterm")

Found easiest way for me. Here an example with fork for clarity that this way is useful for flow control.

import signal
import time
import sys
import os
def handle_exit(sig, frame):
    raise(SystemExit)
def main():
    time.sleep(120)
signal.signal(signal.SIGTERM, handle_exit)
p = os.fork()
if p == 0:
    main()
    os._exit()
    os.waitpid(p, 0)
except (KeyboardInterrupt, SystemExit):
    print('exit handled')
    os.kill(p, 15)
    os.waitpid(p, 0)

The simplest solution I have found, taking inspiration by responses above is

class SignalHandler:
    def __init__(self):
        # register signal handlers
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)
        self.logger = Logger(level=ERROR)
    def exit_gracefully(self, signum, frame):
        self.logger.info('captured signal %d' % signum)
        traceback.print_stack(frame)
        ###### do your resources clean up here! ####
        raise(SystemExit)
        

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.

site design / logo © 2020 Stack Exchange Inc; user contributions licensed under cc by-sa 4.0 with attribution required. rev 2020.4.28.36691