Loading content...
FIFO operations can block—suspend the calling process until a condition is met. This blocking behavior is fundamental to FIFO semantics, providing implicit synchronization between communicating processes. Understanding when and why operations block is essential for building robust IPC systems.
Blocking occurs at three points: open, read, and write. Each has distinct semantics that vary based on the FIFO's state and the flags used.
By the end of this page, you will understand:
• Blocking behavior during open() for readers and writers • Read blocking: when data isn't available • Write blocking: when the buffer is full • O_NONBLOCK effects on all operations • Strategies for handling blocking in production code
The open() system call on a FIFO has unique blocking semantics designed to ensure both ends of the communication channel are ready before proceeding.
Default Blocking Behavior:
| Open Mode | No O_NONBLOCK | With O_NONBLOCK |
|---|---|---|
| O_RDONLY | Blocks until a writer opens | Returns immediately with valid fd |
| O_WRONLY | Blocks until a reader opens | Fails with ENXIO if no reader |
| O_RDWR | Never blocks (rare use) | Never blocks |
Why the Asymmetry?
The different treatment of O_RDONLY vs O_WRONLY with O_NONBLOCK reflects the consequences of proceeding without the other party:
The kernel prevents the second case by refusing the open entirely.
123456789101112131415161718192021222324252627282930313233343536373839404142434445
#include <stdio.h>#include <fcntl.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h>#include <string.h> #define FIFO_PATH "/tmp/blocking_demo.fifo" int main(void) { mkfifo(FIFO_PATH, 0666); printf("=== Testing Open Blocking ===\n\n"); // Test 1: O_RDONLY | O_NONBLOCK - succeeds without writer printf("Test 1: O_RDONLY | O_NONBLOCK\n"); int fd1 = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); printf(" Result: %s (fd=%d)\n\n", fd1 >= 0 ? "SUCCESS" : strerror(errno), fd1); // Test 2: O_WRONLY | O_NONBLOCK - fails (no reader yet, fd1 was closed) close(fd1); printf("Test 2: O_WRONLY | O_NONBLOCK (no reader)\n"); int fd2 = open(FIFO_PATH, O_WRONLY | O_NONBLOCK); printf(" Result: %s\n\n", fd2 >= 0 ? "SUCCESS" : strerror(errno)); // Test 3: O_WRONLY | O_NONBLOCK - succeeds with reader present fd1 = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); printf("Test 3: O_WRONLY | O_NONBLOCK (with reader)\n"); fd2 = open(FIFO_PATH, O_WRONLY | O_NONBLOCK); printf(" Result: %s (fd=%d)\n\n", fd2 >= 0 ? "SUCCESS" : strerror(errno), fd2); // Test 4: O_RDWR | O_NONBLOCK - always succeeds close(fd1); close(fd2); printf("Test 4: O_RDWR | O_NONBLOCK\n"); int fd3 = open(FIFO_PATH, O_RDWR | O_NONBLOCK); printf(" Result: %s (fd=%d)\n", fd3 >= 0 ? "SUCCESS" : strerror(errno), fd3); close(fd3); unlink(FIFO_PATH); return 0;}After successfully opening a FIFO, read operations have their own blocking semantics based on data availability and writer status:
Read Behavior Matrix:
| Condition | Blocking Mode | Non-Blocking Mode |
|---|---|---|
| Data available | Returns data immediately | Returns data immediately |
| No data, writer(s) open | Blocks until data arrives | Returns -1 with EAGAIN |
| No data, no writers | Returns 0 (EOF) | Returns 0 (EOF) |
The EOF Signal:
When all writers close their file descriptors, readers receive EOF (read returns 0). This is the clean termination signal indicating no more data will arrive. However, if a new writer opens the FIFO, data can flow again—EOF isn't permanent.
12345678910111213141516171819202122232425262728293031323334353637383940
#include <stdio.h>#include <fcntl.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h>#include <string.h> #define FIFO_PATH "/tmp/read_blocking.fifo" int main(void) { mkfifo(FIFO_PATH, 0666); // Open both ends (non-blocking to avoid open blocking) int rd_fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); int wr_fd = open(FIFO_PATH, O_WRONLY | O_NONBLOCK); char buf[64]; // Case 1: No data, writer open - EAGAIN printf("Case 1: No data, writer open\n"); ssize_t n = read(rd_fd, buf, sizeof(buf)); printf(" read() returned %zd, errno=%s\n\n", n, n < 0 ? strerror(errno) : "N/A"); // Case 2: Data available - returns data printf("Case 2: Data available\n"); write(wr_fd, "Hello", 5); n = read(rd_fd, buf, sizeof(buf)); printf(" read() returned %zd bytes\n\n", n); // Case 3: No data, no writers - EOF printf("Case 3: No data, no writers (EOF)\n"); close(wr_fd); // Close the writer n = read(rd_fd, buf, sizeof(buf)); printf(" read() returned %zd (0 = EOF)\n", n); close(rd_fd); unlink(FIFO_PATH); return 0;}Servers often open the FIFO for both reading AND writing (or open a separate write fd) to prevent premature EOF. With the server itself as a writer, the FIFO never signals EOF even when all clients disconnect:
int rd_fd = open(FIFO_PATH, O_RDONLY);
int dummy_wr = open(FIFO_PATH, O_WRONLY); // Keeps FIFO open
Write operations block when the kernel buffer is full, providing back-pressure to prevent unbounded memory growth.
Write Behavior Matrix:
| Condition | Blocking Mode | Non-Blocking Mode |
|---|---|---|
| Buffer has space, reader(s) open | Writes immediately | Writes immediately |
| Buffer full, reader(s) open | Blocks until space available | Returns -1 with EAGAIN |
| No readers | SIGPIPE signal + EPIPE error | SIGPIPE signal + EPIPE error |
Buffer Size and Blocking:
The kernel maintains a buffer (typically 64KB on Linux) for FIFO data. When this buffer fills, writers block until readers consume data. This prevents fast producers from overwhelming slow consumers.
The SIGPIPE Problem:
Writing to a FIFO with no readers generates SIGPIPE, which by default terminates the process. Production code must handle this:
1234567891011121314151617181920212223242526272829303132333435
#include <stdio.h>#include <fcntl.h>#include <sys/stat.h>#include <unistd.h>#include <errno.h>#include <string.h>#include <signal.h> #define FIFO_PATH "/tmp/write_demo.fifo" int main(void) { // Option 1: Ignore SIGPIPE globally signal(SIGPIPE, SIG_IGN); mkfifo(FIFO_PATH, 0666); // Open without reader to demonstrate EPIPE int rd_fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); int wr_fd = open(FIFO_PATH, O_WRONLY | O_NONBLOCK); close(rd_fd); // Close reader // Attempt to write with no reader ssize_t n = write(wr_fd, "test", 4); if (n < 0) { printf("Write failed: %s\n", strerror(errno)); // errno == EPIPE when no readers } // Option 2: Use MSG_NOSIGNAL with send() (sockets only) // Option 3: Block SIGPIPE with sigprocmask() per-thread close(wr_fd); unlink(FIFO_PATH); return 0;}In any production code writing to FIFOs (or pipes/sockets), you must handle SIGPIPE. Either:
signal(SIGPIPE, SIG_IGN) — ignore globally, check for EPIPEsigaction() with a custom handlerpthread_sigmask()Otherwise, a disconnected reader crashes your entire process.
POSIX guarantees atomicity for writes up to PIPE_BUF bytes (minimum 512 bytes, typically 4096 on Linux). This guarantee is critical for multi-writer scenarios:
What Atomicity Means:
Implications:
12345678910111213141516171819
#include <stdio.h>#include <unistd.h>#include <limits.h> int main(void) { // PIPE_BUF is defined in <limits.h> printf("PIPE_BUF on this system: %d bytes\n", PIPE_BUF); // Can also query at runtime for a specific file long pipe_buf = fpathconf(STDOUT_FILENO, _PC_PIPE_BUF); printf("Runtime query result: %ld bytes\n", pipe_buf); // Typical values: // Linux: 4096 bytes (4 KB) // macOS: 512 bytes (POSIX minimum) // FreeBSD: 512 bytes return 0;}Non-blocking I/O allows a process to attempt operations without suspending. When combined with select(), poll(), or epoll(), it enables efficient multiplexed I/O.
Converting to Non-Blocking:
12345678910111213141516
#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <errno.h> // Method 1: Open with O_NONBLOCKint fd1 = open("/tmp/fifo", O_RDONLY | O_NONBLOCK); // Method 2: Set O_NONBLOCK after open using fcntlint fd2 = open("/tmp/fifo", O_RDONLY);int flags = fcntl(fd2, F_GETFL);fcntl(fd2, F_SETFL, flags | O_NONBLOCK); // Method 3: Remove O_NONBLOCKflags = fcntl(fd1, F_GETFL);fcntl(fd1, F_SETFL, flags & ~O_NONBLOCK);Poll-Based Event Loop:
123456789101112131415161718192021222324252627282930313233343536373839404142434445
#include <stdio.h>#include <fcntl.h>#include <poll.h>#include <unistd.h>#include <sys/stat.h> #define FIFO_PATH "/tmp/poll_demo.fifo" int main(void) { mkfifo(FIFO_PATH, 0666); int fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK); // Keep dummy writer to prevent immediate EOF int dummy = open(FIFO_PATH, O_WRONLY | O_NONBLOCK); struct pollfd pfd = { .fd = fd, .events = POLLIN, // Interested in read readiness }; printf("Waiting for data on FIFO (poll-based)...\n"); while (1) { int ret = poll(&pfd, 1, 5000); // 5 second timeout if (ret > 0 && (pfd.revents & POLLIN)) { char buf[256]; ssize_t n = read(fd, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; printf("Received: %s\n", buf); } else if (n == 0) { printf("EOF - all writers closed\n"); break; } } else if (ret == 0) { printf("Timeout - no data\n"); } } close(fd); close(dummy); unlink(FIFO_PATH); return 0;}You now have deep understanding of FIFO blocking semantics. Next, we'll explore FIFO limitations—understanding the constraints that may make other IPC mechanisms more suitable for certain use cases.