Why Doesn't Linux Accept() Return Eintr

Why doesn't Linux accept() return EINTR?

Just when I was about to post this, the SA_RESTART flag in strace output caught my attention. signal(2) man page says that signal() "...calls sigaction(2) using flags that supply BSD semantics..." starting from glibc 2.x.

The SA_RESTART flag "...makes certain system calls restartable across signals...", which hides the process of restarting a call from the user. So, this is not specific to accept(), a number of other system calls are also affected, not that there is a clear list of which ones.

So, if you need to react to a signal from a thread that may be blocked on a system call, you should use sigaction() to set your signal handlers, and not signal(). Below is the modified sample program that does exactly that, for reference.

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>

static void sigh(int);

static struct sigaction sa;

int main(int argc, char ** argv) {

int s;
struct sockaddr_in sin;

if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
return 1;
}
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
if (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
perror("bind");
return 1;
}
if (listen(s, 5)) {
perror("listen");
}

memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = sigh;
sigemptyset(&sa.sa_mask);
sigaction(SIGQUIT, &sa, 0);

while (1) {
socklen_t sl = sizeof(struct sockaddr_in);
int rc = accept(s, (struct sockaddr*)&sin, &sl);
if (rc<0) {
if (errno == EINTR) {
printf("accept restarted\n");
continue;
}
perror("accept");
return 1;
}
printf("accepted fd %d\n", rc);
close(rc);
}

}

void sigh(int s) {

sigaction(SIGQUIT, &sa, 0);

unsigned char p[100];
int i = 0;
while (s) {
p[i++] = '0'+(s%10);
s/=10;
}
write(1, "sig ", 4);
for (i--; i>=0; i--) {
write(1, &p[i], 1);
}
write(1, "\n", 1);

}

And strace:

execve("./accept", ["./accept"], [/* 57 vars */]) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 5) = 0
rt_sigaction(SIGQUIT, {0x400994, [], SA_RESTORER, 0x30b7e329a0}, NULL, 8) = 0
accept(3, 0x7fffb626be90, [16]) = ? ERESTARTSYS (To be restarted)
--- SIGQUIT (Quit) @ 0 (0) ---
rt_sigaction(SIGQUIT, {0x400994, [], SA_RESTORER, 0x30b7e329a0}, NULL, 8) = 0
write(1, "sig ", 4) = 4
write(1, "3", 13) = 1
write(1, "\n", 1) = 1
rt_sigreturn(0x1) = -1 EINTR (Interrupted system call)
write(1, "accept restarted\n", 17) = 17
accept(3,

When is EINTR error returned?

Since you mention read and write, I'll assume you're referring to POSIX-compliant operating systems and answer for those. As jwdonahue has pointed out, it may be different for different functions and operating systems.

A return value of EINTR means that the function was interrupted by a signal before the function could finish its normal job. The signal itself may or may not have been caused by an interrupt. Let me elaborate, because the terms "interrupt", "signal" and "interrupted" are subtle.

A signal is simply a particular kind of inter-process communication. It allows the kernel to interrupt the execution of processes and therefore, processes to interrupt each other.

An interrupt, on the other hand, is a lower level, often hardware related phenomenon that originates in the processor. In POSIX environments an interrupt is often turned into a signal by the kernel and sent to relevant processes.

So EINTR means a signal was received. The signal caused the process to be "interrupted" (not to be confused with an interrupt). The signal may or may not have been caused by an underlying interrupt. For example, SIGSEGV and SIGBUS are caused by interrupts while SIGINT (confusingly enough) is caused by software - generally when a Ctrl-C is sent to the terminal.

Why isn't write(2) returning EINTR?

Linux tends to avoid EINTR on writes to/reads from files; see discussion here. While a process is blocking on a disk write, it may be placed in an uninterruptible sleep state (process code D) which indicates that it cannot be interrupted at that time. This depends on the device driver; the online copy of Linux Device Drivers, 3rd Edition is a good reference for how this appears from the kernel side.

You still need to handle EINTR for other platforms which may not behave the same, or for pipes and sockets where EINTR definitely can occur.

Note that you're only writing sizeof(void *) bytes at a time:

char* buf = malloc(1024*1024*128);

if (write(fd, buf, sizeof(buf)) != sizeof(buf))

This should be

const size_t BUF_SIZE = 1024*1024*128;
char* buf = malloc(BUF_SIZE);

if (write(fd, buf, BUF_SIZE) != BUF_SIZE)

Checking if errno != EINTR: what does it mean?

Many system calls will report the EINTR error code if a signal occurred while the system call was in progress. No error actually occurred, it's just reported that way because the system isn't able to resume the system call automatically. This coding pattern simply retries the system call when this happens, to ignore the interrupt.

For instance, this might happen if the program makes use of alarm() to run some code asynchronously when a timer runs out. If the timeout occurs while the program is calling write(), we just want to retry the system call (aka read/write, etc).

When to check for EINTR and repeat the function call?

See sigaction : http://pubs.opengroup.org/onlinepubs/009695399/functions/sigaction.html

SA_RESTART
This flag affects the behavior of interruptible functions; that is, those
specified to fail with errno set to EINTR. If set, and a function specified
as interruptible is interrupted by this signal, the function shall restart
and shall not fail with EINTR unless otherwise specified. If the flag is not
set, interruptible functions interrupted by this signal shall fail with errno
set to EINTR.

By default, you have the SA_RESTART behavior, so you don't have to worry about EINTR, if you don't play with signals.

System calls and EINTR error code

See http://man7.org/linux/man-pages/man7/signal.7.html -- start reading near the bottom where it talks about "Interruption of system calls and library functions..." This is a Linux man page, but the info is pretty generally applicable to any Unix/Posix/Linux-flavored system.



Related Topics



Leave a reply



Submit