FIFO Fum
A blocking FIFO has various gotchas. First up the open needs to be done non-blocking so that the process does not wedge at that point, as presumably you want other parts of the daemon to get up and running first:
inline static void
daemon_startup_foo(void)
{
...
int ret = mkfifo(FIFO_FILE, 0600);
if (ret == -1 && errno != EEXIST) err(1, "mkfifo");
Fd_Notify = open(FIFO_FILE, O_RDONLY | O_NONBLOCK);
if (Fd_Notify == -1) err(1, "open '%s'", FIFO_FILE);
...
Also be sure to use POSIX O_NONBLOCK as that's more portable. Then in the thread that blocks reading from the FIFO the file descriptor must be set back to blocking mode:
static void *
monitor_fifo(void *arg)
{
...
int flags = fcntl(Fd_Notify, F_GETFL, 0);
flags &= ~O_NONBLOCK;
fcntl(Fd_Notify, F_SETFL, flags);
while (1) {
amount = read(Fd_Notify, buf, MAX_MESSAGE_LEN);
...
However, this comes with another gotcha: the file descriptor always returns EOF after a one-and-done writer leaves a message and there is nothing new to read, so either we're back to busy wait-check for new activity, or to have the reader close and reopen the FIFO after each use. When to close is another complication; if one always closes after any read that would only be compatible with one-and-done clients, but would play poorly with a client that can keep the writer end open, another daemon that stays running, perhaps. There's also a chance a one-and-done writer would connect exactly when the reader is between the close and open steps, which could result in lost messages. Maybe the writer could try a few times before giving up, on the off chance that the reader was starting up or something on the first attempt?
Another option might be some sort of DGRAM protocol, though ideally without the "whoops, the packet was lost" thing, and not requiring a network port, as files should give us better permission control. A network port could require encryption and authentication and checksum and verify the messages, but that's getting rather too complicated for my needs. Maybe if there was an easy library for all that…
Still another option would be a persistent database, in which case new records would be added to that, and then drained off by the reader. This would get you database locking and other such benefits, at the complexity of having a database and schema and whatnot. A major advantage is that the reader could be shutdown for maintenance, and that would not affect writers posting message to the database.
select(2)
select(2) is old, but scaling problems matter little when there is but one file descriptor. The goal is to see if select(2) handles the FIFO being marked as EOF better than read(2) does.
Spoilers: FIFO play poorly with select(2), at least on OpenBSD, as far as I can tell, in that the select call is entered and then it wedges and who knows what is going on. So that's strictly worse than just a read(2) on the FIFO.
Leave a Reader Open
Another idea to avoid the EOF problem is to leave a writer open, on the theory that no EOF will be seen if that do-nothing writer never does nothing. However, it could be that any writer closing triggers the EOF state, in which case this idea is a wash. The easiest way to do this is to have the server open the FIFO O_RDWR but never use the write portion… unless it needs to send messages to itself?
// fifo-rdrw - does leaving a writer open in the reader solve the
// always-EOF problem?
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFO_MSGMAX 127U
inline static void
set_blocking(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) err(1, "fcntl");
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) err(1, "fcntl");
}
int
main(int argc, char *argv[])
{
char buf[FIFO_MSGMAX + 1], *fname;
int fd, ret;
ssize_t amount;
if (argc != 2) {
fputs("Usage: fifo-select fifo-name\n", stderr);
exit(1);
}
fname = argv[1];
// Daemon startup stuff, must not block here.
ret = mkfifo(fname, 0600);
if (ret == -1 && errno != EEXIST) err(1, "mkfifo");
if ((fd = open(fname, O_RDWR | O_NONBLOCK)) == -1)
err(1, "open '%s'", fname);
// Elsewhere, the read-from-FIFO code, which in the actual
// daemon will be running in some thread.
set_blocking(fd);
while (1) {
fprintf(stderr, "DBG loop...\n");
amount = read(fd, buf, FIFO_MSGMAX);
if (amount == -1) {
err(1, "read");
} else if (amount == 0) {
warnx("read EOF");
sleep(1);
} else if (amount > 0) {
fprintf(stderr, "MSG %.*s\n", (int) amount, buf);
} else {
errx(1, "read negative??");
}
}
}
$ make fifo-rdwr
cc -O2 -pipe -o fifo-rdwr fifo-rdwr.c
$ ./fifo-rdwr f &
[1] 54630
$ DBG loop...
$ printf foo > f
MSG foo
DBG loop...
$ printf foo > f
MSG foo
DBG loop...
$ printf foo > f
MSG foo
DBG loop...
$ fg
./fifo-rdwr f
^C
This is good, the read(2) call blocks until the next writer comes along, no EOF-on-read problems.
A search on the old web indicates that others arrived at this method to deal with the "FIFO is stuck in EOF state" problem, which would have been a faster if less educational way to come up with this… uh, workaround? Kludge?
"Not invented here" can be a problem, but on the other hand if everyone only blindly copies from that one blog post there's a greater risk of "loss of theory" (as happened in Greece a few centuries after Socrates) and a lack of local domain experts.
Other Programs
Another problem with FIFO is that they can tangle up certain programs,
$ ./fifo-rdwr x
DBG loop...
^C
$ grep foo x
so you may want to keep the FIFO in some other directory tree or in a dot directory to make it more difficult for things like recursive grep to run into and get stalled on the FIFO.