The signal module handles Unix signals—the OS-level notifications for events like Ctrl+C, termination requests, and alarms.
Basic Signal Handler
import signal
import sys
def handler(signum, frame):
print(f"Received signal {signum}")
sys.exit(0)
# Handle Ctrl+C
signal.signal(signal.SIGINT, handler)
# Handle termination request
signal.signal(signal.SIGTERM, handler)
# Program runs...
while True:
passCommon Signals
import signal
# SIGINT (2): Interrupt from keyboard (Ctrl+C)
signal.signal(signal.SIGINT, handler)
# SIGTERM (15): Termination request
signal.signal(signal.SIGTERM, handler)
# SIGHUP (1): Terminal hangup
signal.signal(signal.SIGHUP, handler)
# SIGALRM (14): Timer alarm
signal.signal(signal.SIGALRM, handler)
# SIGCHLD (17): Child process stopped or terminated
signal.signal(signal.SIGCHLD, handler)Note: SIGKILL and SIGSTOP cannot be caught or ignored.
Graceful Shutdown Pattern
import signal
import sys
import time
shutdown_requested = False
def shutdown_handler(signum, frame):
global shutdown_requested
print("\nShutdown requested, finishing current work...")
shutdown_requested = True
signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGINT, shutdown_handler)
def main():
while not shutdown_requested:
# Do work
print("Working...")
time.sleep(1)
print("Cleanup complete, exiting")
sys.exit(0)
main()Timeout with SIGALRM
import signal
class TimeoutError(Exception):
pass
def timeout_handler(signum, frame):
raise TimeoutError("Operation timed out")
def with_timeout(seconds):
def decorator(func):
def wrapper(*args, **kwargs):
# Set alarm
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
return func(*args, **kwargs)
finally:
signal.alarm(0) # Cancel alarm
return wrapper
return decorator
@with_timeout(5)
def slow_operation():
import time
time.sleep(10) # Will timeout after 5 seconds
try:
slow_operation()
except TimeoutError:
print("Operation timed out!")Context Manager for Timeout
import signal
from contextlib import contextmanager
@contextmanager
def timeout(seconds):
def handler(signum, frame):
raise TimeoutError(f"Timed out after {seconds} seconds")
old_handler = signal.signal(signal.SIGALRM, handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, old_handler)
# Usage
with timeout(5):
slow_network_call()Ignore Signals
import signal
# Ignore SIGINT (Ctrl+C won't stop program)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Reset to default behavior
signal.signal(signal.SIGINT, signal.SIG_DFL)Get Current Handler
import signal
# Get current handler
current = signal.getsignal(signal.SIGINT)
if current == signal.SIG_DFL:
print("Default handler")
elif current == signal.SIG_IGN:
print("Signal ignored")
else:
print(f"Custom handler: {current}")Signal in Threads
Signals can only be handled in the main thread:
import signal
import threading
def handler(signum, frame):
print("Signal received in main thread")
# Must be called from main thread
signal.signal(signal.SIGINT, handler)
# In other threads, use events or queues
stop_event = threading.Event()
def worker():
while not stop_event.is_set():
# Work
pass
def signal_handler(signum, frame):
stop_event.set()
signal.signal(signal.SIGTERM, signal_handler)Practical Examples
Web Server Graceful Shutdown
import signal
import sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
server = None
def shutdown(signum, frame):
print("\nShutting down server...")
if server:
server.shutdown()
sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
server = HTTPServer(('', 8000), SimpleHTTPRequestHandler)
print("Server running on port 8000")
server.serve_forever()Worker Process Management
import signal
import os
workers = []
def reap_children(signum, frame):
"""Handle SIGCHLD to clean up zombie processes."""
while True:
try:
pid, status = os.waitpid(-1, os.WNOHANG)
if pid == 0:
break
workers.remove(pid)
print(f"Worker {pid} exited")
except ChildProcessError:
break
signal.signal(signal.SIGCHLD, reap_children)Reload Configuration
import signal
config = {}
def load_config():
global config
with open('config.json') as f:
config = json.load(f)
print("Configuration reloaded")
def reload_handler(signum, frame):
load_config()
signal.signal(signal.SIGHUP, reload_handler)
load_config()
# Send SIGHUP to reload: kill -HUP <pid>Signal Names
import signal
# Get signal name from number
signal.Signals(2).name # 'SIGINT'
# List all signals
for sig in signal.Signals:
print(f"{sig.name}: {sig.value}")Quick Reference
import signal
# Set handler
signal.signal(signal.SIGTERM, handler)
# Handler signature
def handler(signum, frame):
pass
# Special handlers
signal.SIG_IGN # Ignore signal
signal.SIG_DFL # Default behavior
# Get current handler
signal.getsignal(signal.SIGINT)
# Set alarm (SIGALRM after n seconds)
signal.alarm(n)
signal.alarm(0) # Cancel
# Pause until signal received
signal.pause()| Signal | Default | Common Use |
|---|---|---|
| SIGINT | Terminate | Ctrl+C |
| SIGTERM | Terminate | Graceful stop |
| SIGKILL | Terminate | Force kill (uncatchable) |
| SIGHUP | Terminate | Config reload |
| SIGALRM | Terminate | Timeout |
| SIGCHLD | Ignore | Child process events |
signal gives you control over how your program responds to OS events. Use it for graceful shutdowns, timeouts, and process management.
React to this post: