Home > Blockchain >  Undefined behavior when reading from FIFO
Undefined behavior when reading from FIFO

Time:07-09

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

CodePudding user response:

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.

CodePudding user response:

FIFOs have very nuanced semantics. Here's a sequence of events that can produce such a result. W refers top the writing thread. R refers to the reading thread.

W: opens the fifo, is blocked until the reader opens the FIFO
R: opens the fifo
W: open succeeds
W: write succeeds
R: read succeeds
W: closes the fifo
W: back at the top of its loop
W: it opens the fifo again, it succeeds because the reader hasn't closed it yet
W: writes to the fifo
R: reader finally closes the fifo, after it receives the first message
R: reader closes the fifo, this effectively causes W's 2nd message to get flushed down the drain
R: back at the top of its loop
R: opens the fifo, the writer still has it open
R: enters read
W: closes the fifo
R: reads 0 bytes

You have no guarantees, whatsoever, of the relative sequence of multiple events that occurs in both threads. As such, you might get unexpected results.

  • Related