Skip to content

read · sigaction · kill · pipe

The restart that does not happen

A signal arriving during a blocking call interrupts it — unless the handler was installed with SA_RESTART. Whether a read can be interrupted is decided somewhere else entirely.

`EINTR` is not an error about the call

When a signal is delivered to a process parked in a blocking call, the call comes back with EINTR. Nothing went wrong with the read; the kernel needed the process to run its handler and could not leave it asleep.

Which means every blocking call in a program that also handles signals can return EINTR, and a program that treats it as a failure will drop work at random. The correct handling is almost always to try again.

Try this
#include <signal.h>
#include <unistd.h>

void onterm(void) {
  getppid();
}

int main(void) {
  int fds[2];
  char buf[8];
  sigaction(15, "onterm", 0);
  pipe(fds);

  pid_t pid = fork();
  if (pid == 0) {
    kill(getppid(), 15);
    _exit(0);
  }

  return read(fds[0], buf, 8);
}

Watch: What the read returned, and what arrived while it was parked.

eintr-and-the-restart

Unless somebody, somewhere, asked for a restart

SA_RESTART tells the kernel to run the handler and then make the call again from the beginning, so the program never finds out it was interrupted. It is a flag on the handler installation, not on the call.

Which means whether your read can return EINTR depends on how some other part of the program — or a library you linked — installed a handler for a signal you may not have known about. The two pieces of code are nowhere near each other and one silently changes the other's behaviour.

It is also not total: some calls are never restarted whatever the flag says. SA_RESTART reduces how often you have to handle EINTR; it does not remove the need to.

signal() and sigaction() differ here: the older call cannot say SA_RESTART and different Unixes chose different defaults for it. That is most of why portable code uses sigaction.

Try this
#include <signal.h>
#include <unistd.h>

void onterm(void) {
  getppid();
}

int main(void) {
  int fds[2];
  char buf[8];
  /* The third argument is SA_RESTART. */
  sigaction(15, "onterm", 1);
  pipe(fds);

  pid_t pid = fork();
  if (pid == 0) {
    close(fds[1]);
    kill(getppid(), 15);
    _exit(0);
  }

  close(fds[1]);
  return read(fds[0], buf, 8);
}

Watch: The same program, one argument different. What did the read return this time?

eintr-and-the-restart

Trace

Run one of the programs on the left.

Everything you do here stays in this browser.Part of liter8.sh