Search code examples
cnamed-pipesnonblockingfifoposix-select

Reading two consecutive writes from a FIFO using select() and O_NONBLOCK


I'm trying to write two consecutive strings. The issue is that the reader keeps greeting EAGAIN when using O_NONBLOCK on the reader.

Any ideas why it doesn't work when using O_NONBLOCK, shouldn't select() take care of the block?

reader.c

#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

int main() {
    int fd;
    char * myfifo = "/tmp/myfifo";

    mkfifo(myfifo, 0666);
    fd = open(myfifo, O_RDWR | O_NONBLOCK);

    write(fd, "12345678", strlen("12345678"));
    write(fd, "HelloWorld", strlen("HelloWorld"));
    close(fd);

    return 0;
}

writer.c

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>

char buf[BUFSIZ] = { 0 }, buf2[BUFSIZ] = { 0 };
int read1 = 0, read2 = 0;

int main() {
    int fd = 0, a = 0, b = 0 ; 
    char *myfifo= "/tmp/myfifo";

    mkfifo(myfifo, 0666);
    fd_set set;

    if ((fd = open(myfifo, O_RDWR | O_NONBLOCK)) < 0)
        exit(1);

    while (1) {
        FD_ZERO(&set);
        FD_SET(fd, &set);

        if ((select(fd+1, &set, NULL, NULL, NULL)) < 1)
            exit(1);

        if (FD_ISSET(fd, &set)) {
            int total = 0;

            if ((total = read(fd, buf + read1, sizeof(uint32_t) * 2) - read1) <= 0) {
                fprintf(stderr, "%s\n", strerror(errno));
                continue;
            }   
            read1 += total;

            if ((total = read(fd, buf2 + read2, BUFSIZ - read2)) <= 0) {
                fprintf(stderr, "%s\n", strerror(errno));
                continue;
            }   
            read2 += total;

            fprintf(stderr, "%s %d, %d, %s\n", buf, a, b, buf2);
            memset(buf, 0, BUFSIZ);
            memset(buf2, 0, BUFSIZ);
            read1 = read2 = 0;
        }   
    }   

    return 0;
}

Solution

  • You are calling read on the fd twice in the loop, it is possible that the first read read the data available, and the second read will fail with EAGAIN. You should test the readiness with select before performing any read, not just the first one. Because fifos are streams, that means, you have to maintain your own boundary of different pieces of data.

    char buf[BUFSIZ];
    if (FD_ISSET(fd, &set)) {
        int total = 0;
        int off = 0;
        total = read(fd, buf, sizeof buf);
        if (total <= 0) {
                fprintf(stderr, "%s\n", strerror(errno));
                continue;
        }
        // retrieve the data based on how many bytes you have read
        if (total >= sizeof(uint32_t) * 2) {
            ...
        }
    }   
    

    Also, it's recommended to open with O_RDONLY for the read end, and O_WRONLY for the writing end of the fifo.