Python - Python Signal Handling with the signal Module
Python provides the signal module to handle signals sent to a running program by the operating system. A signal is a notification that tells a process that a particular event has occurred. For example, a user pressing Ctrl+C in the terminal generates an interrupt signal that Python normally converts into a KeyboardInterrupt exception.
Signal handling is useful when a program needs to respond gracefully to events such as interruption, termination, alarms, or other operating-system notifications. Instead of allowing a program to stop suddenly, a signal handler can perform cleanup operations, save important information, close resources, or initiate a controlled shutdown.
1. What Is a Signal?
A signal is a software notification sent to a process. Operating systems use signals to communicate events to running programs.
Some commonly encountered signals include:
-
SIGINT: Interrupt signal, commonly generated by pressingCtrl+C. -
SIGTERM: Request to terminate a process. -
SIGKILL: Forces a process to terminate. It cannot be caught or handled by a Python signal handler. -
SIGALRM: Alarm signal generated after a specified amount of time. -
SIGHUP: Traditionally indicates that a terminal or controlling process has been disconnected. -
SIGUSR1andSIGUSR2: User-defined signals available on systems that support them.
The exact signals available can vary between operating systems.
2. The signal Module
Python's built-in signal module provides functions for registering signal handlers and interacting with operating-system signals.
It can be imported using:
import signal
The signal.signal() function is used to associate a signal with a handler function.
Its general form is:
signal.signal(signal_number, handler)
Here, signal_number identifies the signal, while handler is the function that should execute when the signal is received.
3. Creating a Signal Handler
A signal handler is a function that Python executes when the specified signal is received.
Example:
import signal
import time
def handle_signal(signum, frame):
print("Signal received. Preparing to stop the program.")
signal.signal(signal.SIGINT, handle_signal)
while True:
print("Program is running...")
time.sleep(2)
In this example, SIGINT is associated with handle_signal().
When the user presses Ctrl+C, Python invokes the handler instead of immediately terminating the program.
The handler receives two arguments:
signum
frame
signum contains the number of the received signal, while frame represents the current execution frame.
4. Handling SIGTERM
SIGTERM is commonly used to request that a process terminate. It is particularly useful for programs running as background services.
Example:
import signal
import time
running = True
def terminate(signum, frame):
global running
print("Termination request received.")
running = False
signal.signal(signal.SIGTERM, terminate)
while running:
print("Working...")
time.sleep(1)
print("Program stopped safely.")
Here, the handler does not immediately terminate the program. Instead, it changes the value of running to False.
The main loop then finishes naturally, allowing the program to perform any required cleanup before exiting.
5. Using Signals for Graceful Shutdown
One of the most useful applications of signal handling is graceful shutdown.
Suppose a program has opened files, established network connections, or created temporary resources. Abrupt termination could leave those resources in an undesirable state.
A signal handler can notify the main program that it should stop.
For example:
import signal
import time
shutdown_requested = False
def shutdown(signum, frame):
global shutdown_requested
shutdown_requested = True
print("Shutdown requested.")
signal.signal(signal.SIGINT, shutdown)
while not shutdown_requested:
print("Processing data...")
time.sleep(1)
print("Cleaning up resources...")
print("Shutdown complete.")
When Ctrl+C is pressed, the program receives SIGINT, sets the shutdown flag, exits the processing loop, and performs cleanup.
This approach is preferable to placing a large amount of cleanup logic directly inside the signal handler.
6. Signal Handling with SIGALRM
The SIGALRM signal can be used to generate an alarm after a specified number of seconds.
Example:
import signal
import time
def alarm_handler(signum, frame):
print("Time limit reached.")
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(5)
print("Waiting for the alarm...")
while True:
time.sleep(1)
The statement:
signal.alarm(5)
requests an alarm after five seconds.
When the alarm expires, the operating system sends SIGALRM, causing the registered handler to execute.
This mechanism can be useful for certain timeout-related tasks on systems that support SIGALRM.
7. Ignoring a Signal
Python can also be instructed to ignore a particular signal.
For example:
import signal
signal.signal(signal.SIGINT, signal.SIG_IGN)
while True:
print("Running...")
Here, SIGINT is associated with signal.SIG_IGN, which tells the program to ignore the signal.
This means pressing Ctrl+C will not produce the normal interruption behavior.
Ignoring signals should be done carefully because it can make a program difficult to stop.
8. Restoring the Default Behavior
Python provides signal.SIG_DFL to restore the default handling behavior for a signal.
Example:
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
After this assignment, SIGINT is handled according to the operating system's default behavior.
This can be useful when a program temporarily changes signal handling and later wants to restore normal behavior.
9. Important Restrictions
Signal handling has several important restrictions in Python.
In general, Python signal handlers are executed in the main thread of the main Python interpreter. A signal handler should therefore not be treated as an ordinary multithreaded callback.
Signal handlers should also be kept relatively simple. Complicated operations inside a handler can make programs harder to reason about and can introduce unexpected behavior.
A common design is to let the handler set a flag or communicate that shutdown has been requested, while the main program performs the actual work.
10. Signal Handling and KeyboardInterrupt
Python already provides default behavior for SIGINT. When a user presses Ctrl+C, Python normally raises a KeyboardInterrupt exception.
For example:
try:
while True:
print("Running...")
except KeyboardInterrupt:
print("Program interrupted by user.")
This is often sufficient when the only requirement is to handle a user's Ctrl+C interruption.
The signal module becomes more useful when a program needs explicit control over different operating-system signals or needs to coordinate a controlled shutdown.
11. Practical Applications
Signal handling is particularly useful in long-running programs and server applications.
Common applications include:
-
Gracefully stopping web servers.
-
Closing files and database connections before shutdown.
-
Stopping background processing safely.
-
Responding to process termination requests.
-
Implementing certain timeout mechanisms.
-
Reloading configuration in applications that support this behavior.
-
Coordinating shutdown between a parent process and its workers.
-
Handling user interruptions in command-line applications.
12. Example of a Complete Graceful Shutdown
A more practical example combines several ideas:
import signal
import time
running = True
def handle_shutdown(signum, frame):
global running
print("\nShutdown signal received.")
running = False
signal.signal(signal.SIGINT, handle_shutdown)
signal.signal(signal.SIGTERM, handle_shutdown)
try:
while running:
print("Application is working...")
time.sleep(2)
finally:
print("Performing cleanup...")
print("Application stopped.")
This program responds to both SIGINT and SIGTERM.
When either signal is received, the handler changes running to False. The loop then ends, and the finally block performs the cleanup operation.
Conclusion
Python's signal module provides a mechanism for responding to operating-system signals. It is especially useful for long-running applications that need to react to interruptions or termination requests in a controlled way. Signals such as SIGINT, SIGTERM, and SIGALRM can be associated with custom handler functions using signal.signal().
The most important principle is to keep signal handlers simple. Rather than performing extensive operations directly inside a handler, a good design is to notify the main program and allow it to perform cleanup or shutdown in an orderly manner. This makes applications more reliable, maintainable, and easier to control.