Use Signals to Safely Stop processes

Here are some times you probably don't want your process to stop:

  • When saving data to disk or some other resource.
  • When in the middle of some long running job.
  • When doing something business critical.

We can add signal handlers that place your process into a state of stopping the
next time it is ready.

import signal  # (1)
import time
import os

should_quit = False  # (2)

def main():
    stop_signal = signal.SIGUSR1  # (3)
    signal.signal(stop_signal, on_signal)  # (4)

    print(f"Starting - process id is: {os.getpid()}")  # (5)
    print(f"Send {stop_signal} to safely stop")  # (6)

    while not should_quit:
        print("Doing work, please do not interrupt this.")
        time.sleep(3)  # (7)
        print("Done with this unit of work")

    print("Stopping")


def on_signal(signum, frame):  # (8)
    global should_quit

    print("Received request to stop...doing so when it is safe.")
    should_quit = True  # (9)


if __name__ == "__main__":
    main()
  1. The builtin signal module in Python. Most programming languages will provide a similar module for using signals.
  2. We use a global variable(should_quit) to keep track of when the program should stop.
  3. We are choosing SIGUSR1 for this example. There are many different signals, some are reserved for special purposes by your OS or programming language. SIGUSR1 is set aside for programmers to define their own custom handler.
  4. Here we are saying: when my program receives a SIGUSR1, I want you to call the on_signal handler.
  5. We print the process ID to make it easier to send the signal to the correct process.
  6. We print the signal to make it easier to send the correct signal. On my OS, behind the scenes SIGUSR1 maps to 10.
  7. This line represents some task your program is doing that you do not wish to interrupt.
  8. A signal handler is a regular function that takes two parameters signum and frame.
  9. on_signal simply sets should_quit to True, the next time we check against it, the programs stops.

Here is an example run:

$ python program.py
Starting - process id is: 25716
Send 10 to safely stop
Doing work, please do not interrupt this.
Done with this unit of work
Doing work, please do not interrupt this.
Done with this unit of work
Doing work, please do not interrupt this.
Done with this unit of work
Doing work, please do not interrupt this.

We see that our process ID is 25716 and the signal is 10. So to send our signal
we execute:

$ kill -10 25716
Received request to stop...doing so when it is safe.
Done with this unit of work
Stopping

Our processs receives the signal and calls our handler but the program resumes
its work without interruption. The next time around, it sees that we requested
to stop and does so.

17