SIGALRM
EINTR
read() / write()
The Problem: Stuck System Calls
Many system calls block indefinitely โ read() on a terminal waits forever if no input arrives. accept() on a socket blocks until a client connects. How do you impose a maximum wait time?
The classic solution: set a timer, make the system call, handle EINTR when it is interrupted by the signal.
sigaction(SIGALRM) โ install handler, omit SA_RESTART
alarm(N) โ set timeout timer
Make blocking syscall: read(), accept()โฆ
alarm(0) to cancel timer
โ use result normally
SIGALRM โ handler โ syscall returns -1
errno == EINTR โ timeout!
Check errno == EINTR for timeout detection
When SIGALRM fires, the blocking syscall returns -1 with errno = EINTR. You detect the timeout.
The syscall is automatically restarted after the signal. Your timer fires, but the program keeps waiting โ timeout is missed!
There is a theoretical race: if the timer fires AFTER alarm() but BEFORE read() starts, the signal is handled early and read() will block again โ uninterrupted.
In practice: timeout values are typically seconds, making this race extremely unlikely. For sub-second precision use select()/poll() which have built-in timeout parameters.
If the user does not type input within 5 seconds, print “Timed out!”
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#define BUF_SIZE 256
#define TIMEOUT_SECS 5
static void alarm_handler(int sig) {
/* Just return to interrupt the syscall */
(void)sig;
}
int main(void) {
struct sigaction sa;
char buf[BUF_SIZE];
ssize_t n;
int saved_errno;
/* Install SIGALRM handler WITHOUT SA_RESTART */
sa.sa_handler = alarm_handler;
sa.sa_flags = 0; /* NO SA_RESTART โ important! */
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("You have %d seconds to type input: ", TIMEOUT_SECS);
fflush(stdout);
/* Set timer */
alarm(TIMEOUT_SECS);
/* Blocking read โ will be interrupted by SIGALRM */
n = read(STDIN_FILENO, buf, BUF_SIZE - 1);
saved_errno = errno;
/* Always cancel the timer after the call */
alarm(0);
errno = saved_errno;
if (n == -1) {
if (errno == EINTR) {
printf("\nTimed out! No input received.\n");
} else {
perror("read");
}
return 1;
}
buf[n] = '\0';
printf("You typed: %s", buf);
return 0;
}
/* Compile: gcc timed_read.c -o timed_read
Run: ./timed_read
- Type within 5s: prints input
- Don't type: "Timed out!" after 5s
*/
Impose a 3-second maximum wait for a TCP connection to succeed.
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define TIMEOUT_SECS 3
static void timeout_handler(int sig) { (void)sig; }
int connect_with_timeout(const char *ip, int port, int timeout_s) {
int sock;
struct sockaddr_in addr;
struct sigaction sa;
int ret;
/* Install handler without SA_RESTART */
sa.sa_handler = timeout_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, NULL);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) return -1;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &addr.sin_addr);
/* Set timer before blocking connect() */
alarm(timeout_s);
ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
int saved = errno;
alarm(0); /* Cancel timer */
errno = saved;
if (ret == -1) {
close(sock);
if (errno == EINTR) {
fprintf(stderr, "connect() timed out after %ds\n", timeout_s);
} else {
perror("connect");
}
return -1;
}
return sock; /* Connected successfully */
}
int main(void) {
/* Try connecting to a non-responsive host to see timeout */
int fd = connect_with_timeout("192.0.2.1", 80, TIMEOUT_SECS);
if (fd == -1) {
printf("Connection failed (expected on unreachable host).\n");
} else {
printf("Connected! fd=%d\n", fd);
close(fd);
}
return 0;
}
/* Compile: gcc timed_connect.c -o timed_connect
The connect attempt will abort after 3 seconds with EINTR */
A clean wrapper that abstracts the alarm/read/cancel pattern.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
static void noop_handler(int sig) { (void)sig; }
/**
* Read from fd with a timeout.
* Returns:
* >0 : bytes read
* 0 : EOF
* -1 : error (check errno)
* -2 : TIMEOUT
*/
ssize_t timed_read(int fd, void *buf, size_t count, unsigned int secs) {
struct sigaction sa, old_sa;
ssize_t n;
int saved_errno;
unsigned int old_alarm;
/* Install noop handler without SA_RESTART */
sa.sa_handler = noop_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGALRM, &sa, &old_sa);
/* Save any existing alarm and set ours */
old_alarm = alarm(secs);
n = read(fd, buf, count);
saved_errno = errno;
/* Cancel our alarm, restore old one */
alarm(0);
sigaction(SIGALRM, &old_sa, NULL);
if (old_alarm > 0)
alarm(old_alarm); /* Restore previous alarm if any */
errno = saved_errno;
if (n == -1 && errno == EINTR)
return -2; /* Timeout */
return n;
}
int main(void) {
char buf[256];
ssize_t n;
printf("Type something (3s timeout): ");
fflush(stdout);
n = timed_read(STDIN_FILENO, buf, sizeof(buf) - 1, 3);
if (n == -2) {
printf("\nTimeout! Nothing typed.\n");
} else if (n <= 0) {
printf("\nError or EOF.\n");
} else {
buf[n] = '\0';
printf("Got: %s", buf);
}
return 0;
}
/* Clean, reusable, production-worthy pattern.
Returns -2 specifically for timeout, -1 for real errors. */
Have native timeout parameters. No race condition, simpler, and work on multiple fds at once. Preferred over alarm+read.
For socket reads specifically, setsockopt(SO_RCVTIMEO) sets a read timeout at the socket level. Very clean API.
Simple and works for non-socket fds (like terminals, pipes). Has the theoretical race condition but practical in most cases.
Install a SIGALRM handler WITHOUT SA_RESTART, call alarm(N) before read(), then check for EINTR when read returns -1. Always call alarm(0) after to cancel.
SA_RESTART causes the syscall to restart automatically after being interrupted by a signal. With SA_RESTART, the SIGALRM fires but read() restarts silently โ the timeout has no effect.
If the timer fires after alarm() but before read() begins, the signal is handled before the syscall starts. read() then blocks indefinitely with no pending alarm.
EINTR โ “Interrupted system call”. Check errno == EINTR to distinguish a timeout from a real I/O error.
To cancel the pending timer. If the syscall succeeds before the timer fires, the pending SIGALRM would later interrupt an unrelated operation if not cancelled.
