The problem appears to be in checking both r_set and w_set after a single select -- possibly a race condition where the read or write invalidates the result.
This is a robust version, with extra debug, and some results.
It is important to do any available reads before writes. If you give the writes precedence, it will fill the fifo (probably up to 64KB) before the reads get a look-in. This may be part of the race condition in your original code.
// xSelect.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char* argv[]){ if(argc < 2){ puts("err argv"); return -1; } ssize_t r_res, w_res; int r_fd, w_fd; r_fd = open(argv[1], O_RDWR); w_fd = open(argv[1], O_RDWR); fprintf (stderr, "r_fd %d, w_fd %d\n", r_fd, w_fd); fd_set r_set; fd_set w_set; char w_buf[4096]; char r_buf[1235]; for (int j = 0, w = 0; j < 20; j++) { FD_ZERO(&r_set); FD_ZERO(&w_set); FD_SET(r_fd, &r_set); FD_SET(w_fd, &w_set); int ret = select(w_fd + 1, &r_set, &w_set, NULL, NULL); if(FD_ISSET(r_fd, &r_set)){ r_res = read(r_fd, r_buf, sizeof(r_buf)); fprintf (stderr, "Read %zd\n", r_res); } else if(w++ < 3 && FD_ISSET(w_fd, &w_set)){ w_res = write(w_fd, w_buf, sizeof(w_buf)); fprintf (stderr, "Write %zd\n", w_res); } else { fprintf (stderr, "Tick\n"); } } return 0; }
$ make xSelect && echo Run && ./xSelect xSelect.fifo cc xSelect.c -o xSelect Run r_fd 3, w_fd 4 Write 4096 Read 1235 Read 1235 Read 1235 Read 391 Write 4096 Read 1235 Read 1235 Read 1235 Read 391 Write 4096 Read 1235 Read 1235 Read 1235 Read 391 Tick Tick Tick Tick Tick $
EDIT: I may have over-thought this. I do not think the r_set and w_set are directly affected by a single read and/or write: they are however obsoleted by those actions.
In your original code, w_fd will always be writeable until the FIFO is completely full. So if you do equal numbers of long writes and short reads, the writer will eventually hang. If your short reads are 2048 bytes, then you will need about 32 cycles to fill the fifo: if short reads are 48, you will fill the fifo with 17 cycles.
Obviously, the average rates of writing and reading must be equal in the long-term. Typically, the kernel achieves this by not scheduling the writer (which is in a different process), but your code enforces one read, one-write. So the w_set.w_fd eventually becomes unset.
One problem is that the select() does not know how much data you plan to write. If the fifo has space for one byte, w_fd is writeable. I do not know whether the write is truncated, or blocked, or failed, and whether having both fds using the same fifo makes any difference. It is not even worth testing, because the answer may depend on the OS, and whether we are using a fifo or a socket, or some other factor. Your code should be written to deal with a partial write, an error returning EAGAIN, and blocking.
Your code suffers from completely filling the fifo, and mine from completely emptying the fifo unnecessarily after each write. A proper demo should tune (or randomise) its behaviour to explore the possibilities.
char r_buf[2048];, but changing it tochar r_buf[48];or so makes it fail? Or something else?