QUESTION
Does EINTR always make it safe to call the same syscall again?
A syscall interrupted by a signal has not necessarily done nothing. A read/write-family call may return the number of bytes processed as a successful result; it returns EINTR when no progress was made.
SA_RESTART lets the kernel and libc automatically restart some blocking syscalls, but it does not apply to every syscall. For poll-family calls with a timeout, or operations that change user-visible state, the caller must recompute the remaining time.
STRUCTURE
Structure diagram
Even for the same signal, the observed return value depends on whether the syscall started, processed some bytes, or qualifies for automatic restart.
CALL PATH
Call path
Whether to retry depends not on errno alone, but on bytes already processed, the absolute deadline, and side effects of the call.
This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.
SOURCE COORDINATES
Linux 6.18.37 LTS source locations
Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.
| File | Function / structure | What to inspect |
|---|---|---|
| fs/read_write.c | vfs_read(), vfs_write() | Short returns and file-position updates |
| kernel/signal.c | get_signal(), signal_setup_done() | Pending signals and the syscall restart decision |
| arch/x86/entry/common.c | syscall_exit_to_user_mode() | Signal handling before return to userspace |
COMPLETE PROGRAM
Complete runnable example
The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.
cc -std=c17 -Wall -Wextra -O2 eintr_read.c -o eintr_read01#define _POSIX_C_SOURCE 200809L
02#include <errno.h>
03#include <signal.h>
04#include <stdio.h>
05#include <unistd.h>
06
07static volatile sig_atomic_t timed_out;
08
09static void on_alarm(int signo)
10{
11 (void)signo;
12 timed_out = 1;
13}
14
15int main(void)
16{
17 struct sigaction sa = { .sa_handler = on_alarm };
18 char buffer[128];
19
20 sigemptyset(&sa.sa_mask);
21 sigaction(SIGALRM, &sa, NULL);
22 alarm(3);
23
24 for (;;) {
25 ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer));
26 if (n > 0)
27 return write(STDOUT_FILENO, buffer, (size_t)n) < 0;
28 if (n == 0)
29 return 0;
30 if (errno == EINTR && !timed_out)
31 continue;
32 if (errno == EINTR && timed_out)
33 return 2;
34 perror("read");
35 return 1;
36 }
37}
CODE NOTES
Code notes
volatile sig_atomic_t timed_outThis is the smallest integer type that C guarantees can be shared between a signal handler and main. volatile prevents the compiler from eliding accesses, but does not provide thread synchronization.
struct sigaction saSA_RESTART is deliberately omitted so that read() returning EINTR can be observed.
alarm(3)After 3 seconds, SIGALRM becomes pending and wakes the blocking read from its wait.
if (n == 0)EOF is not an error. It is the normal completion condition after all writers of a pipe or file are closed.
errno == EINTR && timed_outEven for the same EINTR, this timeout policy stops rather than retries and reports the reason to the caller above.
DETAILS
Detailed behavior
Kernel restart codes are not errno values
Kernel-internal values such as -ERESTARTSYS and -ERESTARTNOHAND are not exposed directly as errno. The signal disposition and architecture return path decide whether to reexecute the syscall instruction or convert the value to EINTR.
This is why strace may display restart_syscall or ERESTARTSYS.
A relative timeout can grow on every retry
If the original timeout is reused after EINTR, frequent signals can extend the total wait without bound. Establish an absolute CLOCK_MONOTONIC deadline at the start and calculate the time remaining immediately before each new call.
ppoll() and pselect() combine waiting and a signal-mask change in one kernel operation to avoid a wakeup race.
Check state before retrying a call with side effects
For calls such as write, send, and accept that may create an object or consume some data, first check whether a successful byte count or new fd was already returned.
Blindly closing the same fd number again after close() returns EINTR is dangerous. On Linux, the fd slot may be released early and reused by another thread.
OBJECTS
Objects and lifetimes
| Object | Creation and release | Values to inspect |
|---|---|---|
pending signal | Recorded in a thread/process signal queue when generated and consumed at delivery | blocked mask, disposition |
restart_block | Some syscalls preserve the arguments required for restart in the task | restart_fn, remaining time |
deadline | Created by userspace at the start and kept until the operation completes or times out | Measured against CLOCK_MONOTONIC |
FAILURE PATH
Failure conditions and common misconceptions
| Observed symptom | Likely causes | How to verify |
|---|---|---|
| The timeout is longer than expected | The relative timeout is reset for every EINTR | Log the absolute deadline and remaining time |
| Duplicate requests occur | A syscall whose side effect completed is retried blindly | Check the return value and the server request id |
| read keeps returning EINTR | SA_RESTART is not used, or the syscall cannot be restarted | Check the sigaction flags and strace |
LAB
Verify it yourself
- Wait without entering input and verify that SIGALRM leads to exit status 2.
- Add sa.sa_flags = SA_RESTART and compare in strace whether read continues waiting.
- Have a separate process send SIGALRM every 100ms and compare total elapsed time between relative-timeout and absolute-deadline implementations.
./eintr_readstrace -e trace=read,rt_sigaction,alarm ./eintr_readPRIMARY REFERENCES