Skip to content
Advertisement

Linux named fifo non-blocking read select returns bogus read_fds

Similar to the problem asked a while ago on kernel 3.x, but I’m seeing it on 4.9.37. The named fifo is created with mkfifo -m 0666. On the read side it is opened with

int fd = open(FIFO_NAME, O_RDONLY | O_NONBLOCK);

The resulting fd is passed into a call to select(). Everything works ok, till I run echo >> <fifo-name>.

Now the fd appears in the read_fds after the select() returns. A read() on the fd will return one byte of data. So far so good.

The next time when select() is called and it returns, the fd still appears in the read_fds, but read() will always return zero meaning with no data. Effectively the read side would consume 100% of the processor capacity. This is exactly the same problem as observed by the referenced question.

Has anybody seen the same issue? And how can it be resolved or worked-around properly?

I’ve figured out if I close the read end of the fifo, and re-open it again, it will work properly. This probably is ok because we are not sending a lot of data. Though this is not a nice or general work-around.

Advertisement

Answer

This is expected behaviour, because the end-of-input case causes a read() to not block; it returns 0 immediately.

If you look at man 2 select, it says clearly that a descriptor in readfds is set if a read() on that descriptor would not block (at the time of the select() call).

If you used poll(), it too would immediately return with POLLHUP in revents.


As OP notes, the correct workaround is to reopen the FIFO.

Because the Linux kernel maintains exactly one internal pipe object to represent each open FIFO (see man 7 fifo and man 7 pipe), the robust approach in Linux is to open another descriptor to the FIFO whenever an end of input is encountered (read() returning 0), and close the original. During the time when both descriptors are open, they refer to the same kernel pipe object, so there is no race window or risk of data loss.

In pseudo-C:

fifoflags = O_RDONLY | O_NONBLOCK;
fifofd = open(fifoname, fifoflags);
if (fifofd == -1) {
    /* Error checking */
}

/* ... */

/* select() readfds contains fifofd, or
   poll() returns POLLIN for fifofd: */

    n = read(fifofd, buffer, sizeof buffer)
    if (!n) {
        int tempfd;

        tempfd = open(fifopath, fifoflags);
        if (tempfd == -1) {
            const int cause = errno;
            close(fifofd);

            /* Error handling */

        }
        close(fifofd);
        fifofd = tempfd;

        /* A writer has closed the FIFO. */

    } else
        /* Handling for the other read() result cases */

The file descriptor allocation policy in Linux is such that tempfd will be the lowest-numbered free descriptor.

On my system (Core i5-7200U laptop), reopening a FIFO in this way takes less than 1.5 µs. That is, it can be done about 680,000 times a second. I do not think this reopening is a bottleneck for any sensible scenario, even on low-powered embedded Linux machines.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement