“The freedom of birds is an insult to me.”

Python siginterrupt is confusing

The signal module of the Python standard library includes a function called siginterrupt:

signal.siginterrupt(signalnum, flag)

Change system call restart behaviour: if flag is False, system calls will be restarted when interrupted by signal signalnum, otherwise system calls will be interrupted. Returns nothing.

Let's try it:

import os, signal, sys, subprocess

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

# Install a signal handler.
sig = signal.SIGUSR1
signal.signal(sig, sighandler)

# Call `siginterrupt`.
signal.siginterrupt(sig, True)

# Send this process a signal after 1 second.
subprocess.Popen(f"sleep 1 && kill -USR1 {os.getpid()}", shell=True)

# Make a blocking syscall.
b = os.read(sys.stdin.fileno(), 1)
print("read:", b)

siginterrupt(sig, True) should cause os.read to fail with InterruptedError upon delivery of the signal.

In fact, this is not what happens, and the behavior of this program is the same as if siginterrupt had not been called at all: the signal handler runs and the call to os.read is restarted.

What happens if False is passed to siginterrupt instead? In that case, the program continues to hang on read, without calling the signal handler. Once read returns (e.g., because you entered a line in the terminal), the signal handler is called and the program exits.

In neither case does an interrupted syscall terminate without restarting and raise InterruptedError. The only difference is whether the signal handler is called before restarting the syscall (the default) or after (siginterrupt(sig, False)).

We can make sense of this behavior once we consider that Python has two signal handlers. siginterrupt affects the C signal handler: it causes the system call to be restarted immediately after the C signal handler exits, before the EINTR retry loop has a chance to call the Python signal handler. That is why siginterrupt(sig, False) causes the Python signal handler to be delayed.

I don't know when siginterrupt is intended to be used. siginterrupt(sig, True) is a no-op. siginterrupt(sig, False) has observable behavior, but I have difficulty imagining when you would want to defer the execution of your signal handlers until after the interrupted syscall finishes.