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
Bash - How to Match Files Names to Use in Loop
Diff (Gnu Diffutils) 3.6 Exclude Directory
Output Stdout and Stderr to File and Screen and Stderr to File in a Limited Environment
How to Get Docker Commands to Run in the Background with Nohup
How to Find Common Rows in Multiple Files Using Awk
How to Add a System Call via a Lkm
Get the Pid of a Running Playbook for Use Within the Playbook
Python Error "Attributeerror: 'Module' Object Has No Attribute 'Sha1'"
R Programming - Submitting Jobs on a Multiple Node Linux Cluster Using Pbs
How to Pass a Complete Argument List in Bash While Keeping Mulitword Arguments Together
Delete Last Line Break Using Sed
Converting a Visual Studio Makefile to a Linux Makefile
Arm: Disabling Mmu and Updating Pc
Script to Check for Process & Restart Program If Not Found
Why This Bash Function Prints Only First Word of Whole String
Rsync, 'Uid/Gid Impossible to Set' Cases Cause Future Hard Link Failure, How to Fix