EINTR Not Restarting Some Blocking Calls Even With SA_RESTART
0 reputation · 29 Sept 2024, 08:22 UTC
0 reputation · 29 Sept 2024, 08:22 UTC
In Linux, blocking I/O system calls can be interrupted by signals. The kernel returns -EINTR unless the signal handler is installed with the SA_RESTART flag, which instructs the kernel to automatically restart certain system calls.
When a process sets a timeout via alarm() and relies on SA_RESTART, some calls such as read(), write(), and accept() resume automatically, but others like connect() and getaddrinfo() return immediately with -EINTR, even though SA_RESTART is set.
Developers must explicitly set SA_RESTART per signal, and the kernel does not expose a global switch to enable automatic restart for all blocking calls. This leads to inconsistent cancellation behavior across programs that expect a uniform timeout handling mechanism.
1. Would extending SA_RESTART semantics to all blocking system calls simplify timeout handling without breaking existing applications?
2. Is there a kernel‑level configuration or flag that could enable a global “always restart” mode for blocking calls?
3. What compatibility issues might arise on older kernels if such a global restart mechanism were introduced?
There is no kernel-level global switch to force all blocking system calls to restart after a signal. SA_RESTART is a per-signal flag that only affects a fixed, conservative list of "restartable" syscalls. connect() is deliberately excluded from that list and will return -EINTR even when SA_RESTART is set. getaddrinfo() is a glibc (or libc) library function, not a direct syscall, so SA_RESTART does not automatically govern its internal resolver behavior.
Confirmed: On Linux, read(), write(), and accept() on sockets, pipes, and terminals are generally restartable under SA_RESTART. connect() on a blocking socket is not restartable; the kernel returns EINTR to userspace regardless of the flag.
Likely: The getaddrinfo() behavior you observe depends on the C library implementation. glibc's resolver may use non-blocking sockets internally and handle EINTR differently across versions, while other libc implementations may surface the error directly. Because it runs in userspace, the kernel cannot enforce a uniform restart policy for it.
Automatically restarting every interrupted syscall would be unsafe. A signal handler can modify process state, acquire locks, or invalidate assumptions the blocked syscall relied upon. Blindly re-entering the kernel after the handler returns risks deadlocks, double side effects, or corrupted protocol state. The kernel therefore keeps the restartable set small and explicit.
man 7 signal; the set of calls affected by SA_RESTART is documented there and has been stable across mainstream kernels.strace -e trace=connect,read,write,accept -e signal=all to confirm which calls return EINTR when your timer fires.ssize_t n;
do {
n = read(fd, buf, count);
} while (n == -1 && errno == EINTR);
Do not apply this pattern blindly to connect() on a blocking socket; retry semantics are subtle because the connection attempt may already be in progress.alarm(), put the socket in non-blocking mode, start connect(), and wait for writability with a timeout:
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
int r = connect(fd, addr, addrlen);
if (r < 0 && errno == EINPROGRESS) {
struct pollfd pfd = { .fd = fd, .events = POLLOUT };
int pr = poll(&pfd, 1, timeout_ms);
/* check SO_ERROR and pfd.revents */
}
This avoids relying on SA_RESTART entirely and gives you portable timeout control.If your goal is uniform timeout handling, the supported path is userspace: non-blocking I/O plus poll()/epoll_wait() with explicit timeouts, and EINTR retry loops only where the operation is idempotent.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.