Search code examples
cpipepollingfgetc

fgetc still blocking after polling a pipe


Currently, I'm reading lines of input from child programs that are execlp'd. So basically, if a child program fails to execute properly, it shouldn't be piping information when read and an error will be thrown.

I attempted polling the file descriptor and it returns one no matter if the program was executed correctly. So basically I get past poll then fgetc is hanging/blocking as there is nothing to read, but fgetc is also not returning -1.

Reading and Polling:

char* read_line(int fd) {
    // fd is a pipe's read end. I know it reads properly.
    FILE *file = fdopen(fd, "r"); 
    int ret;
    struct pollfd fdinfo[1];
    fdinfo[0].fd = fd;
    fdinfo[0].events = POLLIN;
    ret = poll(fdinfo,1, 1000);
    if (ret < 0) {
        return "NOPE";
    }   
    char* result = malloc(sizeof(char) * 80);
    memset(result, 0, sizeof(int));
    int position = 0;
    int next = 0;
    while (1) {
        next = fgetc(file); //STALLING HERE
        if (next == '!') {
                free(result);
                return "!";
        }
        if (next == EOF || next == '\n') {
            result[position] = '\0';
            return result;
        } else {
            result[position++] = (char)next;
        }
    }
}

Solution

  • You are not adequately checking the information provided to you by poll(). You do detect the case that poll() reports an error by returning -1, but it doing otherwise does not guarantee that there are any data available to read. A negative return value indicates that poll() failed to do its job; it does not tell you about the state of any of the polled file descriptors.

    In the first place, poll() returns 0 if it times out. In that case, your code just rolls on ahead and attempts to read from the file descriptor, which will block unless data happen to arrive between the return from poll() and the call to fgetc().

    In the second place, poll() signals problems with a designated file descriptor by setting one or more of the corresponding revent bits, and reporting an event on that FD (in part by returning a positive number). Specifically,

    poll() sets the POLLHUP, POLLERR and POLLNVAL flag in revents if the condition is true, even if the application did not set the corresponding bit in events

    and

    A positive [return] value indicates the total number of pollfd structures [...] for which the revents member is non-zero

    (POSIX 1003.1-2008; emphasis added)

    You might very well see a POLLHUP event if the child process writing to the other end of the pipe crashes. You could see a POLLNVAL event if your program screws up its file descriptor handling in certain ways, and you always have to allow for the possibility of an I/O error, which poll() signals as a POLLERR event.

    Thus, to avoid blocking, you should attempt to read from the pipe only when poll() returns 1 and signals POLLIN among the events on the file. Conceivably, you might see that together with a POLLERR or a POLLHUP. I would recommend aborting if ever there is a POLLERR, but a POLLHUP is to be expected when the write end of the pipe is no longer open in any process, and that does not invalidate any data that may still be available to read (which would be signaled by an accompanying POLLIN).