I have two different programs in C++ that talk to each other via (linux) pipes.
One process is the master, and the other process is the slave. Either of them could crash or simply be restarted, but never both at the same time (let's assume this).
Slave creates and opens pipe A in write mode in a blocking fashion (irrelevant last feature), then creates and opens pipe B in read mode in a blocking fashion as well (again, irrelevant last feature).
Master creates and opens pipe A in read mode in a non-blocking fashion (again, irrelevant last feature), then it waits some time so that slave can exit blocking create of pipe A and block in create of pipe B, then creates and opens pipe B in write mode in a non-blocking fashion as well (again, irrelevant last feature).
int open_pipe_read_mode(const char *file_path, bool dont_block)
{
int pipe_fd = -1;
if ((mkfifo(file_path, 0666) != 0) && (errno != EEXIST)) {
return pipe_fd;
}
if (dont_block) {
pipe_fd = open(file_path, O_RDONLY | O_NONBLOCK); // For write mode: O_WRONLY | O_CREAT | O_NONBLOCK
} else {
pipe_fd = open(file_path, O_RDONLY); // For write mode: O_WRONLY | O_CREAT
}
return pipe_fd;
}
So far so good as long as both ends are up and running. Note from my code that actually only one process will create the pipe with mkfifo, and the other one will fail with EEXIST.
My question arises when one of the two processes is restarted. If the other process doesn't use the pipes in the downtime, will the restarted process crush/fail upon calling my functions open_pipe_read_mode or open_pipe_write_mode ? Or, since these pipes are created with mkfifo as files, it is possible to reopen one just like that? Note that in my code I consider (ignore) the EEXIST error from mkfifo.
I have been reading in other questions like this one but all I find is "you cannot" or "why would you? just don't". I believe that creating pipes without filenames indeed makes it impossible (i.e. cumbersome and not safe, definitely not recommended) to reopen them as I intend.
Is there any recommendation against this (namely, if it only works surfing the undefined behaviour effect yet it could not work at any random point)?
My actual question is: considering that a pipe has been created with a file name with mkfifo and whose sides have been opened by two different processes, is it possible to open one end of the pipe (be it read or write, blocking or not) after such end has previously been closed and while the other end has been kept open?
My code works as described before restarting either process, but I couldn't manage to test a restart scenario because my project is complex. Before developing a simple couple of programs (mostly because working them once says nothing about determinism let aside good practices) I decided to look in the internet and I was surprised to find no question/answer to this.
What I have tested
Two very basic programs master and slave who open two pipes A and B to exchange information. Pipe A is slave-to-master flow, pipe B is master-to-slave flow. The exchange of information is irrelevant, just here to check SIGPIPE and other errors.
int master_main()
{
// Open pipe A as read (non-blocking)
pid_t pid = getpid();
int read_pipe_fd = open_pipe_read_mode("./test_pipe_A", true);
if (read_pipe_fd < 0) {
printf("%d\tError open_pipe_read_mode\n", pid);
return 1;
}
sleep(1); // Wait for slave program to open pipe B
// Open pipe B as write (non-blocking)
int write_pipe_fd = open_pipe_write_mode("./test_pipe_B", true);
if (write_pipe_fd < 0) {
printf("%d\tError open_pipe_write_mode\n", pid);
return 1;
}
int data_var = 1234;
do {
// Write data
if (write(write_pipe_fd, (int*)&data_var, sizeof(int)) < 0) {
printf("%d\tError write\n", pid);
} else {
printf("%d\tWrote data_var = %d\n", pid, data_var);
}
// Read data
ssize_t count = read(read_pipe_fd, &data_var, sizeof(int));
if (count != sizeof(int)) {
printf("%d\tError read\n", pid);
} else {
printf("%d\tRead data_var = %d\n", pid, data_var);
}
data_var++;
sleep(10);
} while (true);
}
int slave_main()
{
// Open pipe A as write (blocking)
pid_t pid = getpid();
int write_pipe_fd = open_pipe_write_mode("./test_pipe_A", false);
if (write_pipe_fd < 0) {
printf("%d\tError open_pipe_write_mode\n", pid);
return 1;
}
// Open pipe B as read (blocking)
int read_pipe_fd = open_pipe_read_mode("./test_pipe_B", false);
if (read_pipe_fd < 0) {
printf("%d\tError open_pipe_read_mode\n", pid);
return 1;
}
int data_var = 5678;
do {
// Write data
if (write(write_pipe_fd, (int*)&data_var, sizeof(int)) < 0) {
printf("%d\tError write\n", pid);
} else {
printf("%d\tWrote data_var = %d\n", pid, data_var);
}
// Read data
ssize_t count = read(read_pipe_fd, &data_var, sizeof(int));
if (count != sizeof(int)) {
printf("%d\tError read\n", pid);
} else {
printf("%d\tRead data_var = %d\n", pid, data_var);
}
data_var++;
sleep(10);
} while (true);
}
Then I execute:
slave_main &
master_main
967 Wrote data_var = 5678
967 Read data_var = 1234
970 Wrote data_var = 1234
970 Read data_var = 5678
^C
master_main
971 Wrote data_var = 1234
971 Error read
967 Wrote data_var = 1235
967 Read data_var = 1234
971 Wrote data_var = 1235
971 Read data_var = 1235
^C
[1]+ Broken pipe slave_main
Until ^C, the flow is working.
I restart the master program and it works fine. The error read happens because pipe is opened in non-blocking mode and slave is still sleeping and thus hasn't sent anything yet. In the next loop we see read/write works fine for both ends.
When I kill the master, slave crashes after waking up upon write operation, probably SIGPIPE signal.
If I do the vice versa version (master in bg, killing slave, etc) I get similar result: the reopen works.
My question remains open. My code seems to work on linux but: is it deterministic? safe? (not) recommended? These questions refer to reopening pipes with mkfifo (I acknowledge there are workarounds to what I want to achieve, but my question is rather theoretical for the sake of knowledge, and in a way, to be able to use it with care when it's needed).
SIGPIPEsignal orEPIPEerror if you've disabled the signal. Reading from the pipe when the other end has closed it will return EOF. You need to detect either of these conditions and reopen the pipe.O_CREATand it does not already exist as a named pipe your program will create an ordinary file.