Communicate between 2 processes using named pipes in perl?

Viewed 420

I'm trying to solve this problem using named pipes:

I have one process that runs continuously. This process should just check the named pipe if there is any data available there. If not, I have replacement data (cached data) to use for this situation.

It's very important, that this one continuously running process, does not block, and if there's no process attached at the "other end" of the named pipe, this continuously running process should resort to cached data.

Then I have a second process. This process can be started at-will, but will normally be run from a cron script at regular intervals. This process, will dump data into the named pipe.

Ideally, I would want this data to be just dumped into the named pipe, waiting for it to be available for the continuously running process to pick up at any time. The continuously process could hold its pipe open at all times if possible, or it could open the pipe when it needs data.

But if its required, this intermittialy running process could hold its pipe open until data is processed by the continiously running process.

Any good ideas to accomplish this? I suspect I could somehow open a pipe, even if theres no process on the other end without blocking, but of course there would be no data until a process attaches on other end of the named pipe and sends data.

And when this process detaches, will buffered data still be available for the reading application, even if the read is made after the detachment?

1 Answers

I am not sure that you need to go to that trouble for the described purpose. Why not dump information into a plain file, which the ever-running process can check for and read? Or, use a socket which is far more flexible? But what you ask can be done of course.

The writer gets blocked at opening the FIFO until the reader opens it, what is OK from what I read in the question. The reader can check in a non-blocking way by using a select. But then it's simpler that the writer creates the pipe in the first place and deletes it once it's done, so the reader knows that there is no new data when there's no file to open,.

Writer

use warnings;
use strict;
use feature 'say';

my $fifo = 'test.fifo';

if (-e $fifo) { unlink $fifo  or die "Can't unlink $fifo: $!" }

system('mkfifo', $fifo) and system('mknod', $fifo, 'p') 
    and die "Can't make $fifo: $!";

open my $fh, '>', $fifo or die "Can't open fifo $fifo: $!";  # Blocks here until
say $fh "Message to fifo from $$";                           # there's a reader
close $fh;

unlink $fifo or die "Error unlinking $fifo: $!";

Reader, the ever-running program

use warnings;
use strict;
use feature 'say';

use Errno qw(ENOENT);

my $fifo = 'test.fifo';

while (1) { 
    say "\nDo work (sleep a little really), try for message ...";
    sleep 3;

    if (my $msg = read_fifo($fifo)) { 
        say $msg;
        #last;     # done with this exercise
    }

    # Run the writer program in another terminal at some point
    # Or, to simulate that:
    #my $pid = fork // die "Can't fork: $!";
    #if ($pid == 0) { exec $writer_path }
}
    
sub read_fifo {
    my ($fifo) = @_;
    open my $fh, '<', $fifo  or do {
        if ($! == ENOENT) {
            say "No file $fifo -- no new data, carry on and use default";
            return;
        }
        else { die "Can't open fifo $fifo: $!" }  # some other error
    };
    return join '', <$fh>;
}

This leaves out more testing that would be appropriate, and yet other details.

In my take all this shows firstly that communicating over a plain file is far simpler in this case.


The problem got rounded with a lot of context, provided in comments and a chat. One detail substantially changes the problem: the writer actually can't wait. Here is a way to handle that.

The writer needs to do other things after making data available, picked up by the reader at an unspecified time, so it cannot wait at the pipe. Then fork a process in the writer and deal with the FIFO in that child process, while the parent is free to do what it needs.

Writer from above, now handling FIFO in a child process

use warnings;
use strict;
use feature 'say';

my $fifo = 'test.fifo';

my $pid = fork // die "Can't fork: $!";

if ($pid == 0) {  
    if (-e $fifo) { unlink $fifo  or die "Can't unlink $fifo: $!" }

    system('mkfifo', $fifo) and system('mknod', $fifo, 'p') 
        and die "Can't make $fifo: $!";

    open my $fh, '>', $fifo or die "Can't open fifo $fifo: $!"; # awaits reader
    say $fh "Message to fifo from $$";
    close $fh;
    
    unlink $fifo or die "Error unlinking $fifo: $!";

    exit;
}

# parent can now do its work

# Always reap -- but now also must not leave until FIFO's been read
my $gone = waitpid $pid, 0;
if ($gone > 0) { say "FIFO-handling child exited with $?" }

This can be tested the same way as the initial writer, with the same reader.

The writer's child is waiting on the pipe in a blocked open, until the reader gets to go to read it. As the reader opens the pipe for reading the writer's open completes and the data gets written, and is received by the reader. Or there is no FIFO at all and the reader's open fails (with ENOENT) so the reader knows that there is no new data and can use its default.

Related