Search code examples
cpipeposixposix-select

Why is the second or looped select() blocking?


I'm getting blocked at a select() while working with a pipe. The first select() will unblock when we get data on stdin (I just type one letter and press Enter).

Then I write data to the write-end of a pipe, but the select() doesn't recognize any data on the pipe's read fd. So it blocks indefinitely.

int    fd_pipe[2];
char   buf[20];
fd_set set;

pipe(fd_pipe);

FD_ZERO(&set);
FD_SET(fd_pipe[0], &set); // Pipe read fd
FD_SET(0         , &set); // stdin fd

select(fd_pipe[0]+1, &set, NULL, NULL, NULL); // I type 1 char + ENTER to get past this point

if (FD_ISSET(0, &set))
{
    read(0, buf, 20);          // Can confirm we do get here
    write(fd_pipe[1], buf, 1); // Lets put just one character in the pipe
}

select(fd_pipe[0]+1, &set, NULL, NULL, NULL); // <-- We get stuck here

if (FD_ISSET(fd_pipe[0], &set))
{
    char d;
    read(0, &d, 1);
    assert(d == buf[0]);
}

If I do this without select(), things work fine:

int fd_pipe[2];
pipe(fd_pipe);

char c = 'x';
write( fd_pipe[1], &c, 1);

c = 'a';
read( fd_pipe[0], &c, 1);

assert(c == 'x');

In the larger picture, I have a multi-threaded program which is using select() and pipe() to emulate canceling a read() operation. I write to the pipe with a single character with the intent of forcing select() to return so I can shutdown the operation instead of trying to send a signal to cancel a blocking read() on the main FD. But select() doesn't return.


Solution

  • The select function modifies the sets you pass to it. When select return the sets will contain only the active descriptors.

    In your case only STDIN_FILENO will be set, so the second call to select will not have fd_pipe[0] in it.

    The solution is actually not to re-add the pipe to the set and call select a second time, but to only call select once:

    FD_ZERO(&set);
    FD_SET(fd_pipe[0], &set); // Pipe read fd
    FD_SET(0         , &set); // stdin fd
    
    select(fd_pipe[0]+1, &set, NULL, NULL, NULL); // I type 1 char + ENTER to get past this point
    
    if (FD_ISSET(0, &set))
    {
        read(0, buf, 20);          // Can confirm we do get here
        write(fd_pipe[1], buf, 1); // Lets put just one character in the pipe
    }
    else if (FD_ISSET(fd_pipe[0], &set))
    {
        char d;
        read(fd_pipe[0], &d, 1);
        assert(d == buf[0]);
    }
    

    You also need to check what select actually returns. It could return -1 which means there's an error.


    If you do need to call select more than once. Then you'll need to re-zero/re-set the fd_set.