Search code examples
c++cipcnamed-pipesmkfifo

Undefined behavior when reading from FIFO


I am trying to send data from javascript to C via a named pipe/FIFO. Everything seems to be working other than every couple messages there will be an additional iteration of the loop reading 0 bytes rather than the expected 256. I realize I could add something like if (bytes_read>0) {... but this seems like a band aid to a bigger problem.

From my testing it looks like the open() call in the reader is unblocked when the writer opens AND closes the file. It looks like sometimes the reader is ready (and blocked on open) for the next message before the writer closes the file from the previous so it will run through its loop twice per message.

Is this just expected behavior or am I misunderstanding something? Any advice would be greatly appriciated!

Writer:

uint32_t writer(buf)
{
  int fd;

  // FIFO file path
  char * myfifo = "/tmp/channel";
  
  // Creating the named file(FIFO)
  // mkfifo(<pathname>, <permission>)
  mkfifo(myfifo, 0666);
  
  // Open FIFO for write only
  fd = open(myfifo, O_WRONLY);

  uint32_t written = write(fd, buf, sizeof(char)*256);
  close(fd);

  return written;
}

Reader:

int main()
{
    int fd;

    // FIFO file path
    char * myfifo = "/tmp/channel";

    // Creating the named file(FIFO)
    mkfifo(myfifo, 0666);

    while (1)
    {
        char buf[256] = "\0";

        // Open FIFO for Read only
        fd = open(myfifo, O_RDONLY);

        // Read from FIFO
        int bytes_read = read(fd, buf, sizeof(char)*256);

        struct access_point *dev = (struct access_point *) &buf;

        printf("Bytes read: %i\n", bytes_read);
        printf("ssid: %s\n", dev->ssid);

        int r = close(fd);
        //sleep(0.01);
    }
    return 0;
}

After 5 calls to writer the output will look like:

# Call 1 (bad)
Bytes read: 256
ssid: kremer
Bytes read: 0
ssid: 

# Call 2
Bytes read: 256
ssid: kremer

# Call 3
Bytes read: 256
ssid: kremer

# Call 4 (bad)
Bytes read: 256
ssid: kremer
Bytes read: 0
ssid: 

# Call 5
Bytes read: 256
ssid: kremer

Solution

  • A reador 0 bytes indicates the pipe was closed on the other end. So this is absolutely expected behavior.

    So you do have to catch that 0 and read again so the kernel waits for the next time the pipe is opened.

    Note: read can also return -1 with errno set to EINTR. Same deal, just read again.