1

I use a FIFO (named pipe) for IPC. Now process A calls

  • mkfifo(path)
  • open(path)

Naturally open() will block until the file is written to by process B. Now I need a way to invalidate a FIFO. Therefore I call

  • unlink(path)

Now I expected that any blocking open call would return, but they don't and my process hangs indefinitely.

How can I unblock the open call when the FIFO is unlinked? Do I have to resort to O_NONBLOCK?

PS: I tried the suggested write/unlink/close approach to no avail. The open call blocks immediately.

void invalidate() {
    int fd = open(path, O_WRONLY)
    unlink(path)
    close(fd)
}

I think the issue is

The FIFO must be opened on both ends (reading and writing) before data can be passed. Normally, opening the FIFO blocks until the other end is opened also.

However invalidate should work without knowing if the FIFO is currently opened for reading or not.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    Unlinking a file has no effect on processes that already reference the file. – Barmar Jun 10 '20 at 20:09
  • I don't think there's a way to do what you want. – Barmar Jun 10 '20 at 20:10
  • You can use non-blocking, and the program will have to periodically check if the link still exists. – Barmar Jun 10 '20 at 20:10
  • I tried nonblocking, but that brings other problems with it, namely I can't distinguish zero byte `write`s to signify the end of a stream. –  Jun 11 '20 at 19:59
  • If there's nothing to read, `read()` returns `-1` and sets `ERRNO = EWOULDBLOCK`. – Barmar Jun 11 '20 at 20:08
  • I tried that, but I got very inconsistent results for `EWOULDBLOCK`. Sometimes it would set `EWOULDBLOCK` and return `0`, most of the time it would not even set `EWOULDBLOCK`... idk what's going on here. –  Jun 11 '20 at 20:09
  • Well, that's the way it's supposed to work. Something's obviously wrong with the way you're coding things, because the answer below should also work and doesn't work for you. – Barmar Jun 11 '20 at 20:15
  • Well I can't see what could go wrong just calling mkfifo/open/read in order –  Jun 11 '20 at 20:17
  • 1
    The call to `open(path, O_RDONLY)` should return as soon as the other process does `open(path, O_WRONLY)`, or vice versa. – Barmar Jun 11 '20 at 20:19
  • As soon as I do the `open` when invalidating, that one hangs as well. –  Jun 11 '20 at 20:32
  • Are you opening in a loop? The previous one should return, then the next one will hang. – Barmar Jun 11 '20 at 20:34
  • I don't understand your last comment. The first open should be hanging when it starts. Then another process opens the FIFO in the other direction, and the first one should unhang. – Barmar Jun 11 '20 at 20:35
  • Does it work if you DON'T try to invalidate it? – Barmar Jun 11 '20 at 20:36
  • I think I know what the issue is. For this approach to work, I would need to be aware if the FIFO is currently opened for reading by someone else. "The FIFO must be opened on both ends (reading and writing) before data can be passed. Normally, opening the FIFO blocks until the other end is opened also." –  Jun 11 '20 at 21:08
  • It is not a given that the FIFO has to be opened at the time of invalidation, needs to work for both cases. Updated my question. –  Jun 11 '20 at 21:08

3 Answers3

1

The invalidator should open the FIFO in non-blocking write mode. This way, its call to open() won't hang if there are no readers waiting for it. It should then close the FIFO immediately.

Any processes that were waiting in a read-only open will return when the FIFO is opened. Then they'll immediately read EOF.

void invalidate() {
    int fd = open(path, O_WRONLY | O_NONBLOCK);
    unlink(path);
    close(fd);
}
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • I think I figured it out now. I needed a combination of `open` with `O_NONBLOCK` and handling `EINTR` on the other side. –  Jun 11 '20 at 21:31
  • If the other side is opening for writing, closing the read side will cause it to get a `SIGPIPE` signal when it tries to write. – Barmar Jun 11 '20 at 21:32
  • I made a small modification by using `O_RDWR` to also unblock any waiting `open(fd, O_WRONLY)` calls. –  Jun 12 '20 at 09:15
0

Open the FIFO for writing, unlink it, then close it. This will make process A's open succeed, and the resulting FD will immediately be at EOF. (By the way, the unlink is unnecessary if you just want to make open return, but is still a good idea for cleaning up.)

0

On Linux you can (nonportably) open a FIFO in O_RDWR mode. When you do and hold the FIFO, other openers won't get blocked in their open calls (regardless of whether those are readonly or writeonly open calls). Then you can unlink the FIFO while still holding the O_RDWR file handle to it.

Example code:

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int C, char **V)
{
    int fd;
    if(0>(fd=open(V[1], O_RDWR))) return perror("open"),1;
    if(0>unlink(V[1])) return perror("unlink"),1;
}
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • Oh, I read that a bit late sry, figured the `O_RDWR` trick out myself in the meantime ^^ –  Jun 12 '20 at 09:16
  • @ErikAigner It semi-works on MacOS too. At least the `O_RDWR` part. But there it seems to only unblock one opener waiting on that fifo. On Linux it unblocks all of them (e.g., say you've got multiple `cat fifo` or `cat fifo`). On Cygwin I get EPERM for the O_RDWR. Could be worked around with two threads I guess. – Petr Skocik Jun 12 '20 at 09:45