I have a situation where I need to check if the other side of a fifo has opened it, however I can't use a open because otherwise the program will start doing stuff.
Why I have to do this: I have a program (a monitor) that launches a server program (both created by me). The monitor uses this fifo to communicate beacause the monitor can be closed/reopened while the server is already started.
The problem is when the monitor starts the server: in this case I have to wait in some way for fifos to be created and then open them.
Actually I'm using a while on the monitor that checks when the fifos are created, however in this way it opens the fifos before the server could do it (even if the instruction after the mkfifo is actually the open!!!).
You may say that I'm opening the fifos in wrong order on the monitor (I'm opening the Write fifo(WRONLY) before the read fifo), the problem is that I can't revert this order because it's required that the server will wait for clients on the open (RDONLY) fifo.
Any suggestion on how to avoid this race condition?
I'm actually using a sleep in the monitor after checking if fifos are created, this obviusly solves the problem but I think is definitely not correct.
Thanks to everyone
Edit 1:
This is how things are working at the moment
Server
mkfifo(fifo1)
mkfifo(fifo2)
open(fifo1 O_RDONLY)
open(fifo2 O_WRONLY)
Monitor
while (fifo1 doesn't exists && fifo2 doesn't exists);
open(fifo1 O_WRONLY)
open(fifo2 O_RDONLY)
I think the race condition is quite explicit now, it's important to notice that fifos are blocking (only RDONLY are blocking, WRONLY will not block even if there isn't anyone on the other side => this is a unix behaviour, not designed by me).
Edit 2:
The race condition happens at first fifo open level. I must open the first fifo on the server before the monitor does it.
You may want to use a named semaphore using sem_open()
that is visible to each program at the file-system level in order to synchronize the two programs. Basically your monitor program will wait on the locked semaphore until the server increases it. At that point, all the fifo's will be initialized, and you can move foward with your monitor with the fifo's in a known-state.
Make sure to use the O_CREAT
and O_EXCL
flags on the initial call to sem_open()
so that the creation of the semaphore is atomic. For example, both the monitor and the server program will attempt to create the semaphore at startup if it doesn't already exist ... if it exists, the call will fail, meaning the either the monitor or the server, but not both programs, gained the right to create the semaphore and initialize it. The monitor then waits on the semaphore while the server initializes the fifo's ... once the fifo's are initialized, the server releases the semaphore, and the monitor is then able to continue.
Here's the process as I'm envisioning it ... I believe it should effectively solve your race condition:
In the monitor:
- Create the named semaphore and set it in a locked state
- Launch the server
- Wait for the FIFO's to be visible at the file-system level
- Open
fifo1
for writing (non-blocking)
- Open
fifo2
for reading (blocks until the server opens fifo2
for writing)
- Wait on the semaphore (possibly with a timeout) until the server unlocks it, indicating that it has now opened both FIFO's successfully.
In the server:
- Create the FIFO's
- Open
fifo1
(blocks until the monitor opens it for writing)
- Open
fifo2
(non-blocking)
- Unlock the semaphore now that the server has opened both FIFO's
So basically your monitor cannot continue until there is a "known-state" where everything is properly initialized ... the server indicates that state to the monitor via the named semaphore.
If you do the open()s in the right order, there is no race condition. (the only possible race would be a third process interfering with the same fifos) From the fine manual :
"However, it has to be open at both ends simultaneously before you can proceed to do any input or output operations on it. Opening a FIFO for reading normally blocks until some other process opens the same FIFO
for writing, and vice versa."
This means that the ordering
{ process 1: open (fifo1, RO); process2: open (fifo1, WO); }
...
{ process 1: open (fifo2, WO); process2: open (fifo2, RO); }
will always succeed (given no process starvation) The order of operations on each fifo is unimportant; for fifo1 either process1 or process2 can go first (and will be blocked until the other side succeeds).
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define FIFO1 "fifo1"
#define FIFO2 "fifo2"
void do_master(void);
void do_slave(void);
void randsleep(unsigned max);
/************************************/
void do_master(void)
{
int in,out, rc;
char buff[20];
randsleep(5);
mkfifo(FIFO1, 0644);
randsleep(7);
mkfifo(FIFO2, 0644);
out = open(FIFO2, O_WRONLY);
if (out == -1) {
fprintf(stderr, "[Master]: failed opening output\n" );
return;
}
fprintf(stderr, "[Master]: opened output\n" );
in = open(FIFO1, O_RDONLY);
if (in == -1) {
fprintf(stderr, "[Master]: failed opening input\n" );
close(out);
return;
}
fprintf(stderr, "[Master]: opened input\n" );
rc = write( out, "M2S\n\0" , 5);
fprintf(stderr, "[Master]: wrote %d\n", rc );
rc = read( in, buff , sizeof buff);
fprintf(stderr, "[Master]: read %d: %s\n", rc, buff );
unlink(FIFO1);
unlink(FIFO2);
}
/***********************************/
void do_slave(void)
{
int in,out, rc;
unsigned iter=0;
char buff[20];
loop1:
in = open(FIFO2, O_RDONLY);
if (in == -1) {
fprintf(stderr, "[Slave%u]: failed opening input\n", ++iter );
randsleep(2);
goto loop1;
}
fprintf(stderr, "[Slave]: opened input\n" );
loop2:
out = open(FIFO1, O_WRONLY);
if (out == -1) {
fprintf(stderr, "[Slave%u]: failed opening output\n", ++iter );
randsleep(3);
goto loop2;
}
fprintf(stderr, "[Slave]: opened output\n" );
rc = write( out, "S2M\n\0" , 5);
fprintf(stderr, "[Slave]: wrote %d\n", rc );
rc = read( in, buff , sizeof buff);
fprintf(stderr, "[Slave]: read %d:%s\n", rc, buff );
}
/*************************************/
void randsleep(unsigned max)
{
unsigned val;
val = rand();
val %= max;
sleep(val);
return;
}
/*************************************/
int main(void)
{
int rc;
switch (rc=fork()) {
case -1: exit(1); break;
case 0: do_slave(); break;
default: do_master(); break;
}
exit (0);
}
Consider using a Unix domain sockets of type SOCK_STREAM
. The server will bind
its socket to a name in the filesystem. Each client gets its own bidirectional connection to the server.
At least what I found 'till now makes me understand that there isn't a way to detect if a fifo is opened, except if you open it too.
Edit 1:
As Jason stated, there are two ways (both not allowed in my homework however):
1) *You could do a search through /proc/PID/fd (replace PID with a numerical process ID) to see what client processes had already opened your FIFO*
2) Another option would be to call fuser on your FIFO
However, the first one requires something that teachers don't want: watching inside proc/PID/fd. The second one I heared requires root privileges, and this is again something I can't do. Hopefully this will help someone else in the future.