EINTR on write(): avoiding duplicate data when retrying
0 reputation · 28 Apr 2022, 05:02 UTC
0 reputation · 28 Apr 2022, 05:02 UTC
The write() system call may return EINTR when a signal interrupts the operation before any data is transferred. POSIX requires the caller to retry, but the standard does not clarify whether data written before the interruption is discarded. This ambiguity can lead to duplicate writes on regular files.
Determine a reliable strategy for retrying write() that guarantees no data duplication across POSIX systems.
EINTR guaranteed to have written zero bytes?EINTR?When a signal interrupts a write() call, the kernel returns EINTR. The POSIX specification does not state that the write has written zero bytes; the kernel may have committed a partial write. On Linux the kernel actually writes any bytes that fit into the page cache before delivering the signal that causes EINTR. This means a retry loop that blindly restarts the entire write can duplicate data.
POSIX requires the application to retry, but it does not guarantee that a write that returns EINTR has written nothing. The return value of the first write() call must be treated as the number of bytes already written.
On Linux the following sequence occurs for a regular file:
EINTR is pending, the kernel delivers it after the copy.write() call returns the number of bytes copied, which may be less than the requested length.The portable way to avoid duplication is to keep track of how many bytes were actually written and resume from that offset. A typical wrapper looks like this (simplified C code):
ssize_t safe_write(int fd, const void *buf, size_t count) {
size_t written = 0;
const char *ptr = buf;
while (written < count) {
ssize_t n = write(fd, ptr + written, count - written);
if (n == -1) {
if (errno == EINTR) continue; // retry from current offset
return -1; // other error
}
written += n;
}
return written;
}
Key points:
EINTR only; other errors should propagate.Setting O_APPEND on the file descriptor guarantees that each write() call appends to the end of the file, eliminating duplication even if the kernel writes partial data. However:
write().Standard C libraries do not provide a built‑in retry wrapper. Common third‑party libraries such as libuv or libevent expose safe write helpers, but writing a small loop as shown above is portable and has zero external dependencies.
To verify that your retry logic works on your target system, compile a small test program that writes a large buffer to a regular file while a SIGALRM handler sends a signal during the write. Use strace to confirm that the first write() returns a partial count followed by EINTR, and that your loop writes the remaining bytes without duplication.
If you need strict ordering across multiple writers, let me know so we can discuss alternatives like file locking or atomic rename patterns.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.