“You are clever, O Samana.”

Blocking a signal is different than ignoring it

Signal handling in Python
  • 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.sleep and signal interrupts
  • Python siginterrupt is confusing

On Unix-like systems, the disposition of a signal determines what a process does when the signal is delivered. It can be one of:

The disposition of a signal is controlled by the sigaction function in C. Other languages usually have an equivalent; in Python, it's signal.signal.

Independent of a signal's disposition, you can choose to mask or block a signal, using pthread_sigmask. Blocking is temporary (though it may be indefinitely long). Blocking a signal via pthread_sigmask is not the same as ignoring a signal via sigaction, because ignored signals are discarded while blocked signals are left pending. If a signal was generated when blocked and the signal is subsequently unblocked, then the signal will be delivered and the signal's disposition determines what happens.

Let's see the difference in practice. First, we'll register a signal handler:

import signal

def sighandler(_signo, _frame):
    print("signal received")

sig = signal.SIGUSR1
signal.signal(sig, sighandler)

Then we mask the signal, raise it to our own process, and then unmask it.

signal.pthread_sigmask(signal.SIG_BLOCK, {sig})
signal.raise_signal(signal.SIGUSR1)
print("==> raised signal while masked")
signal.pthread_sigmask(signal.SIG_UNBLOCK, {sig})
print("==> unmasked signal")

This prints:

==> raised signal while masked
signal received
==> unmasked signal

Now, try ignoring a signal, raising it, and then un-ignoring it:

signal.signal(sig, signal.SIG_IGN)
signal.raise_signal(signal.SIGUSR1)
print("==> raised signal while ignored")
signal.signal(sig, sighandler)
print("==> restored sighandler")

This prints:

==> raised signal while ignored
==> restored sighandler

The signal handler was never called.

A final interesting case: what if a signal was both blocked and ignored when generated?

signal.pthread_sigmask(signal.SIG_BLOCK, {sig})
signal.signal(sig, signal.SIG_IGN)
signal.raise_signal(signal.SIGUSR1)
print("==> raised signal while masked and ignored")
signal.signal(sig, sighandler)
signal.pthread_sigmask(signal.SIG_UNBLOCK, {sig})
print("==> restored sighandler and unmasked")

This prints:

==> raised signal while masked and ignored
signal received
==> restored sighandler and unmasked

The disposition of the signal is checked when it is delivered (after the second call to pthread_sigmask), not when it is generated.