hello_world has asked for the wisdom of the Perl Monks concerning the following question:

Hi Folks, I seem to be having a problem doing a simple IPC between a child and a parent process. The child process runs a command and writes the output into a pipe which the parent reads. My program seems to be getting into some sort of a blocking state when i do  @from_pipe = <READX>,but works for  $from_pipe = <READX>. I have this code as an example.Is my program suffering from buffering ?? Thanks !!
#!/usr/bin/perl use strict; use warnings; use POSIX; use IO::Handle; # pipe (READ,WRITE); # sub _Forked { my $ip = shift; die "Couldn't fork" unless defined (my $pid = fork()); if ($pid == 0) { # CHILD my @ping = `ping $ip -w 2 -q | sed -n '\$p'`; close (READ); select WRITE; print "@ping\n"; close (WRITE); # exit (0); } # return ($pid); } # sub main { my $cpid = _Forked('4.2.2.2'); my $dpid; do { $dpid = POSIX::waitpid(-1,WUNTRACED); my @val = <READ>; print "@val\n"; # my $cpid = _Forked('4.2.2.2'); } until ($dpid < 0) } main();

Replies are listed 'Best First'.
Re: Reading from a pipe
by Eliya (Vicar) on Nov 20, 2011 at 10:25 UTC

    You have to close the writing end of the pipe in the parent. The fork duplicates the pipe's handles, and the readline (i.e. my @val = <READ>) doesn't stop reading until the pipe is fully closed (i.e. in both child and parent).

    This should work:

    ... sub _Forked { my $ip = shift; pipe (READ,WRITE); die "Couldn't fork" unless defined (my $pid = fork()); if ($pid == 0) { # CHILD my @ping = `ping $ip -w 2 -q | sed -n '\$p'`; close (READ); select WRITE; print "@ping\n"; close (WRITE); # exit (0); } # close WRITE; # <--- return ($pid); } ...

    Note that you have to move the creation of the pipe into the _Forked() routine (otherwise you'd have a dysfunctional pipe with a closed writing end upon subsequent calls of the routine...).

    That said, I'm not sure why you're using an array here, as you seem to be outputting the last line of the ping only, anyway.  But maybe this is just an oversimplified example.

      Thanks it works,Lets say if i fork 3-4 child processes and all of them write into the pipe,and on the parent side can i read at a time what all child processes wrote by doing a  @read_from_children = <$read>. Looks it doesn't work.I still have to do a loop and read.

        The problem is that the readline blocks until it has read a line (irrespective of whether you use it in list or scalar context). So despite your non-blocking waitpid(-1,WUNTRACED) you won't start a new child before the old one has written a line to the pipe. In other words, as you have it, your pings will still run sequentially (just as they would without the additional fork/wait wrapped around the backticks call).

        If you want to run several child processes in parallel, you'd have to have independent pipes, and (for example) read from them in a select loop when they're ready (the other form of select, that is, not the one you're already using in your code).

Re: Reading from a pipe
by hello_world (Acolyte) on Nov 20, 2011 at 15:47 UTC
    Thanks for the explanation Eliya.Basically,i am writing a script which forks multiple processes which ping different hosts for 40-50 sec and report to the parent script with values. I'd like to do the pinging part in parallel,not sequentially. So,if i have a 10 child ping processes,do i need to create 10 different pipes ( hope i am getting this right).Lastly,what sort of IPC is better in such cases.

      Here's a minimal example using piped open (instead of manually creating the pipes), and IO::Select as a more convenient wrapper around the select builtin:

      use IO::Select; sub spawn_ping { my $addr = shift; open my $fh, "-|", "ping $addr -w 2 -q | sed -n '\$p'" or die $!; return $fh; } my $sel = new IO::Select(); for (1..10) { my $addr = 'localhost'; # or whatever $sel->add( spawn_ping($addr) ); } while ( my @ready = $sel->can_read ) { for my $fh (@ready) { my $resp = <$fh>; if (defined $resp) { print STDERR $resp; # or do with it whatever you like } else { $sel->remove($fh); close $fh; } } }

      Note that the <$fh> would (temporarily) block until a whole line is available on the respective pipe. This shouldn't be a problem in this particular case, though, as you're outputting entire lines.