Search code examples
clinuxpipeglibc

Non-blocking read on pipe


Can one do non-blocking I/O on a pipe? fcntl fails to set O_NONBLOCK. Page 918 of The Linux Programming Interface includes a table 'Semantics of reading n bytes from pipe or FIFO (p)'. This table lists the behaviour of pipes and FIFO's with one column titled O_NONBLOCK enabled? This would imply that you can set the O_NONBLOCK flag on a pipe. Is this correct? The following code fails to set the flag, fcntl(2) does not report an error though.

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    int fds[2];
    pid_t pid;
    char wr_buf[100];
    char rd_buf[100];

    pipe(fds);

    pid = fork();

    if ( pid )
    {
        while (1 )
        {
            memcpy( wr_buf, "abcdefghi\0",10);
            write( fds[1], wr_buf, 10);
            sleep(2);
        }
    }
    else
    {
        int retval = fcntl( fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
        printf("Ret from fcntl: %d\n", retval);
        while (1)
        {
            ssize_t r=read( fds[0], rd_buf, 10 );
            printf("read: %d\n", r);

            if ( r > 0 )
            {
                printf("Buffer: %s\n", rd_buf);
            }
            else
            {
                printf("Read nothing\n");
                perror("Error was");
                sleep(1);
            }
        }
    }
}

Solution

  • There is nothing special to pipe and O_NONBLOCK. The following example work as expected. I did not check every retval from every call to make the example a bit more readable. A real world application must do the checks.

    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    
    int main()
    {
        int fds[2];
        pid_t pid;
        char buf[100];
    
        pipe(fds);
    
        pid = fork();
    
        if ( pid )
        {
            while (1 )
            {
                memcpy( buf, "abcdefghi\0",10);
                write( fds[1], buf, 10);
                sleep(2);
            }
        }
        else
        {
            int retval = fcntl( fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
            printf("Ret from fcntl: %d\n", retval);
            while (1)
            {
                ssize_t r=read( fds[0], buf, 10 );
                printf("read: %d\n", r);
    
                if ( r > 0 )
                {
                    printf("Buffer: %s\n", buf);
                }
                else
                {
                    printf("Read nothing\n");
                    perror("Error was");
                    sleep(1);
                }
            }
        }
    }
    

    After writing my example I inspect your code and found:

    flags = fcntl(pfd[0], F_GETFD);
    flags |= O_NONBLOCK;
    if (fcntl(pfd[0], F_SETFD, flags))
    

    Please change F_SETFD to F_SETFL and also for the get operation. You would not change the file descriptor flags but the file status flags :-)

    From man 3 fcntl:

    File descriptor flags The following commands manipulate the flags associated with a file descriptor. Currently, only one such flag is defined: FD_CLOEXEC, the close-on-exec flag. If the FD_CLOEXEC bit is 0, the file descriptor will remain open across an execve(2), otherwise it will be closed.

    File status flags Each open file description has certain associated status flags, ini‐ tialized by open(2) and possibly modified by fcntl(). Duplicated file descriptors (made with dup(2), fcntl(F_DUPFD), fork(2), etc.) refer to the same open file description, and thus share the same file status flags.

    F_SETFL (int) Set the file status flags to the value specified by arg. File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored. On Linux this command can change only the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK flags. It is not possible to change the O_DSYNC and O_SYNC flags; see BUGS, below.