Skip to Content
Course content

252: The signal Module for Handling OS Signals

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're a server in a busy restaurant. You're in the middle of taking a complex order from a customer—writing down appetizers, mains, and drinks. Suddenly, your manager taps you on the shoulder and whispers, "Table 4 needs their check right now."

You don't just freeze in place or drop your notepad and walk away. Instead, you acknowledge the manager, quickly handle the request (or make a mental note to do it the second you finish the current sentence), and then get back to the order you were taking. You've essentially "handled" an interrupt without crashing your current task.

In the world of operating systems, that manager's tap is a signal. Your Python program is the server. By default, when Python receives certain signals—like when you hit Ctrl+C in your terminal—it just panics and throws a KeyboardInterrupt. But using the signal module, you can tell Python: "When you feel this specific tap on the shoulder, don't crash; run this specific function instead."

Tapping your program on the shoulder

To make this work, we use signal.signal(). This function takes two arguments: the signal you're looking for and the "handler" (a function) you want to run when that signal arrives. The most common signal you'll encounter is SIGINT, which is what the OS sends when you trigger an interrupt (like Ctrl+C).

import signal
import time
import sys

def graceful_exit(signum, frame):
    print(f"\nReceived signal {signum}. Cleaning up my desk before I leave...")
    # This is where you'd close database connections or save state
    sys.exit(0)

# We tell the OS: "If you send SIGINT, run the graceful_exit function"
signal.signal(signal.SIGINT, graceful_exit)

print("I'm doing some heavy lifting. Try hitting Ctrl+C!")
while True:
    time.sleep(1)
    print("Still working...", end="\r")

Notice that the graceful_exit function takes two arguments: signum (the signal number) and frame (the current stack frame). You might not always use the frame argument, but Python insists on passing it, so you have to include it in your signature or your code will crash the moment the signal hits.

Handling the "Termination" request

While SIGINT is for humans hitting keys, SIGTERM is what the OS or a process manager (like Docker or systemd) sends when it wants your program to shut down politely. It's basically saying, "I'm going to kill you in a few seconds, so please wrap things up now."

I've seen far too many developers ignore SIGTERM. When they do, the OS eventually loses patience and sends SIGKILL, which is the equivalent of a sudden power outage—no cleanup, no saved files, just an immediate stop. I highly recommend mapping both SIGINT and SIGTERM to the same cleanup function in any production-grade daemon or background worker you write.

The main thread limitation

Here is a quirk that catches people off guard: signals can only be caught by the main thread.

If you've spun up a bunch of threading.Thread workers, they cannot register their own signal handlers. If a signal arrives, the main thread is the only one that can "feel" it. If you need your worker threads to shut down, your main thread's signal handler should set a global threading.Event or a flag that the workers check periodically. It's a bit of extra plumbing, but it's the only way to ensure your entire application shuts down cleanly.




📋 Practical Task

Build a Signal-Aware File Processor

You are building a script that simulates processing a large batch of data. To prevent data corruption, the script creates a "lock file" called processing.lock when it starts. If the script is killed abruptly, that lock file stays on the disk, preventing the script from running again until someone deletes it manually.

Your Task: Write a Python script that does the following:

  • Creates a file named processing.lock at the start of execution.
  • Enters an infinite loop simulating work (use time.sleep()).
  • Implements a signal handler for both signal.SIGINT and signal.SIGTERM.
  • The handler must:
    1. Print a message saying "Shutting down and removing lock file...".
    2. Delete the processing.lock file using the os module.
    3. Exit the program cleanly.

Test your solution by running the script and interrupting it with Ctrl+C. Verify that the processing.lock file is gone after the program exits.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.