- Python has two signal handlers
- Blocking a signal is different than ignoring it
- Python doesn't pass
SA_RESTART - Safe signal idioms
- Python signal handlers are unexpectedly reentrant
time.sleepand signal interrupts- Python
siginterruptis confusing
On Unix-like systems, signals are low-level asynchronous1 notifications. A program can register a handler function that runs when a signal is delivered.
When a signal is received, the kernel pauses execution of the program2 and arranges for the signal handler to be called, on the same call stack as the rest of the program. A program cannot control when this happens: it might be deep inside a standard library function, or in a critical section with its data structures in an inconsistent state.
The list of C standard-library functions that signal handlers can safely call is severely limited. printf is not safe to call in a signal handler. Neither is malloc. Nor is it safe to take a lock: if the process already holds the lock, then the signal handler will block, but the interrupted process cannot make progress because it is waiting for the signal handler to return.
The Python standard library has an interface, signal.signal, to register your own Python function as a signal handler. Python itself is a C program and has a C signal handler. However, because of the restrictions on signal handlers, it would be patently unsafe to call the user's Python signal handler and execute arbitrary Python code inside the C signal handler. Instead, the C signal handler sets a flag which the main loop of the interpreter checks periodically. If the flag is set, the interpreter calls the Python signal handler at a point when it is safe to do so.
Because the Python signal handler is separate from the C signal handler, the strict rules on signal-safe functions do not apply to Python signal handlers. It's OK to call to print and to allocate memory. You do not need to worry that your program was interrupted in the middle of list.append, as the interpreter will not let that happen.
You do still need to worry about race conditions with your own Python code.
def transfer_money(sender, recipient, amount):
with lock:
change_balance(sender, -1 * amount)
change_balance(recipient, amount)
A signal could arrive between the two calls to change_balance, when the account balances are in a temporarily inconsistent state. And if the signal handler tried to take the lock, it would deadlock for the same reason that C signal handlers can deadlock.
The same advice holds whether you are in C or in Python: do as little as possible in the signal handler itself, and defer the real work to your program's main loop.