Search code examples
c++linuxselectposixnonblocking

Why does select on a named pipe with no writers block indefinitely?


I call select with a single named pipe fd in read_fds. This named pipe has no writers and has only been opened in non-blocking, read only mode. I would expect that the select returns with the named pipe fd marked as ready to read, and that trying to read from the pipe returns 0:

From the manpage on read:

When attempting to read from an empty pipe or FIFO:

  • If no process has the pipe open for writing, read() shall return 0 to > indicate end-of-file.

However, select just blocks indefinitely. Why is this the case?

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#include <stdexcept>
#include <thread>
#include <iostream>

int main()
{
    char buf[4096];

    // Create a named pipe
    auto err = mkfifo("/tmp/whatever",0666);
    if(err) {
        throw std::runtime_error(
                    std::string("Failed to create fifo ")+
                    strerror(errno));
    }

    std::thread reader_thread(
                [&](){
        auto fd = open("/tmp/whatever",O_RDONLY|O_NONBLOCK);
        if(fd < 0) {
            throw std::runtime_error("Failed to open fifo");
        }

        fd_set fds;
        while(1) {
            FD_ZERO(&fds);
            FD_SET(fd,&fds);
            std::cerr << "calling select" << std::endl;
            auto retval = select(fd+1,&fds,nullptr,nullptr,nullptr);
            if(retval < 0) {
                std::runtime_error("Failed to call select");
            }

            if(FD_ISSET(fd,&fds)) {
                auto read_bytes = read(fd,buf,4096);
                std::cerr << "read " << read_bytes << std::endl;
                if(read_bytes==0) {
                    break;
                }
            }
        }

        close(fd);
    });

    reader_thread.join();

    return 0;
}

Solution

  • From the POSIX documentation fo select:

    A descriptor shall be considered ready for reading when a call to an input function with O_NONBLOCK clear would not block, whether or not the function would transfer data successfully. (The function might return data, an end-of-file indication, or an error other than one indicating that it is blocked, and in each of these cases the descriptor shall be considered ready for reading.

    ...

    If none of the selected descriptors are ready for the requested operation, the pselect() or select() function shall block until at least one of the requested operations becomes ready, until the timeout occurs, or until interrupted by a signal.

    From the pipe(7) manpage (which is the underlying object of a FIFO):

    If all file descriptors referring to the write end of a pipe have been closed, then an attempt to read(2) from the pipe will see end-of-file (read(2) will return 0).

    Mind the usage of present perfect tense! This implies the FIFO has to be opened on both sides fiorst, the closed on the writer side (for your application) to generate an EOF condition.

    So, unless the fifo is eventually closed by the writer, why should select return? The setting for the (fifo-)file itself is irrelevant for good reason: It would introduce a race-condition between opening on both sides when using the most efficient method to read more than one byte at a time. That's the normal way for e.g. a command pipe: start the reader process and lateron the writer (which is typically a completely unrelated program when using a named pipe).

    If you want select to return early, use the timeout argument. But normally, one uses a seperate thread which can be terminated by a signal (see the select man-page for more information).

    As a sidenote: One nice thing about Linux/POSIX is that it does not really matter whether you use a FIFO or a file or your microphone driver.