Back Original

Python doesn't pass `SA_RESTART`

“I'm deceased, maimed or in Philadelphia.”

Syscalls that block1 can be interrupted by signals. If your process registers a signal handler and the signal is delivered while the process is in a blocking syscall, then the signal handler will be called and the syscall will terminate and return the error code EINTR. (This behavior is the origin of the phrase "worse is better".)

Often, you want to restart a syscall that is interrupted by a signal handler. One way to do this is to wrap every syscall that may block in a loop:

while (1) {
    ssize_t r = read(fd, buf, sz);
    if (r < 0) {
        if (errno == EINTR) {
            continue;
        } else {
            perror("read");
            exit(1);
        }
    } else {
        break;
    }
}

A different way is to pass SA_RESTART when registering the signal handler with sigaction. This instructs the kernel to automatically restart any syscalls that were interrupted, and saves the programmer the trouble of writing EINTR loops everywhere.

Although SA_RESTART seems more convenient, it is not what CPython does. As described in PEP 475, system calls in the standard library check for EINTR and loop.

This is because Python has two signal handlers, and SA_RESTART is a kernel feature that applies to the C signal handler only, not the Python signal handler. If CPython passed SA_RESTART to sigaction when it registered the C signal handler, then the syscall would be immediately restarted after the C signal handler exited. The Python signal handler would not have a chance to run until the syscall returned, and so delivery of the signal could be delayed indefinitely from the perspective of the Python program. To ensure that signals are delivered promptly even when in a blocking syscall, CPython checks for EINTR and calls the Python signal handler before manually restarting the syscall.

The signal.signal function does not accept a SA_RESTART flag because CPython's implementation of signals gives it SA_RESTART-like semantics by default.