9
\$\begingroup\$

I have code that needs to exit gracefully. To simplify things, I am presenting an example based on the answer given here.

Since I need to handle a few signals, I thought of putting this logic in a function:

def set_signals():
   original_sigint = signal.getsignal(signal.SIGINT)
   signal.signal(signal.SIGINT, exit_gracefully)
   signal.signal(signal.SIGTERM, exit_gracefully)
   signal.signal(signal.SIGINT, exit_gracefully)
   signal.signal(signal.SIGALRM, exit_gracefully)
   signal.signal(signal.SIGHUP, signal.SIG_IGN)

Thus, the main block of the python code should be:

if __name__ == '__main__':
    # store the original SIGINT handler
    set_signals()
    run_program()

This will fail however, since the exit_gracefully does not know the variable original_init.

Therefore, my solution was to create original_sigint as a global variable.

import signal
import time
import sys


original_sigint = None


def run_program():
    while True:
        time.sleep(1)
        print("a")


def exit_gracefully(signum, frame):
    # restore the original signal handler as otherwise evil things will happen
    # in raw_input when CTRL+C is pressed, and our signal handler is not re-entrant
    global original_sigint
    signal.signal(signal.SIGINT, original_sigint)

    try:
        if raw_input("\nReally quit? (y/n)> ").lower().startswith('y'):
            sys.exit(1)

    except KeyboardInterrupt:
        print("Ok ok, quitting")
        sys.exit(1)

    # restore the exit gracefully handler here
    signal.signal(signal.SIGINT, exit_gracefully)


def set_signals():
    global original_sigint
    original_sigint = signal.getsignal(signal.SIGINT)
    signal.signal(signal.SIGINT, exit_gracefully)
    signal.signal(signal.SIGTERM, exit_gracefully)
    signal.signal(signal.SIGINT, exit_gracefully)
    signal.signal(signal.SIGALRM, exit_gracefully)
    signal.signal(signal.SIGHUP, signal.SIG_IGN)


if __name__ == '__main__':
    # store the original SIGINT handler
    set_signals()
    run_program()

This approach works, but I am not happy with the use of another global variable (the code I working on is quite and is already littered with those). So the question is, how would you do it differently?

\$\endgroup\$
7
\$\begingroup\$

What about implementing your signal handling code inside a class? This could look something like the following:

class GracefulExit:
    def __enter__(self):
        # set up signals here
        # store old signal handlers as instance variables

    def __exit__(self, type, value, traceback):
        # restore old signal handlers

You can then use this in your code as follows:

with GracefulExit():
    # Signals will be caught inside this block.

# Signals will no more be caught here.

You'll find more examples of how to use the with-statement on the web.

\$\endgroup\$
  • 1
    \$\begingroup\$ Hm, this looks much more pythonic :) \$\endgroup\$ – Nobody Jun 16 '14 at 7:59
  • \$\begingroup\$ See also: contextlib.contextmanager, which allows you to use the with statement above without explicitly implementing __enter__ and __exit__. \$\endgroup\$ – Adam Jun 16 '14 at 8:09
  • \$\begingroup\$ @Nobody or #codesparkle can you provide simple example of signal handling which is implemented with "with" statement? \$\endgroup\$ – Khamidulla Apr 22 '15 at 7:38
  • \$\begingroup\$ @Phoenix: If you did not find an answer to your question yet, feel free to ask it on the appropriate SE site (probably SO). Codereview is the wrong platform for this question. However, you should note that the signal handling itself is not done via with. It is only used to set and reset the signal handlers that do the actual work (which might or might not involve a with statement). \$\endgroup\$ – Nobody Apr 22 '15 at 17:27
  • \$\begingroup\$ This is just brilliant. \$\endgroup\$ – CVVS Apr 2 '18 at 16:15
2
\$\begingroup\$

You can avoid the global by passing the original handler as function parameter and binding it with a lambda in set_signals:

def exit_gracefully(signum, frame, original_sigint):
#...

def set_signals():
    original_sigint = signal.getsignal(signal.SIGINT)
    bound_exit_gracefully = lambda signum, frame: exit_gracefully(signum, frame, original_sigint)
    signal.signal(signal.SIGINT, bound_exit_gracefully)
    signal.signal(signal.SIGTERM, bound_exit_gracefully)
    signal.signal(signal.SIGINT, bound_exit_gracefully)
    signal.signal(signal.SIGALRM, bound_exit_gracefully)
    signal.signal(signal.SIGHUP, signal.SIG_IGN)

The naming could also be improved a bit e.g.:

  • set_signals -> setup_grafecul_signal_handler
  • original_sigint -> original_sigint_handler
  • exit_gracefully -> gracefull_exit_signal_handler
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.