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.
#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.
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.
#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?