I am attempting to learn the functionality of parents, children and pipes in perl. My goal is to create a single pipe (not bidirectional) that reads from the command line, and prints it through a pipe. Referencing the pids many many times.
The code so far:
#!/usr/bin/perl -w
use warnings;
use strict;
pipe(READPIPE, WRITEPIPE);
WRITEPIPE->autoflush(1);
STDOUT->autoflush(1);
my $parentpid = $$;
my $childpid = fork() // die "Fork Failed $!\n";
# This is the parent
if ($childpid) {
&parent ($childpid);
close READPIPE;
close WRITEPIPE;
waitpid ($childpid,0);
print "Parent: The child pid with a pid of: $childpid as stopped, and the parent with the pid: $parentpid has stopped.\n";
exit;
}
# This is the child
elsif (defined $childpid) {
&child ($parentpid);
close WRITEPIPE;
}
else {}
sub parent {
while(1){
my $input = <STDIN>;
if ($input eq "\n"){print "Undef- CRTL+C = Quit\n", last;}
else {
print "ParentSub: The parent pid is: ",$parentpid, " and the message being received is: ", $input;
print WRITEPIPE "$input";
print "ParentSub: My parent pid is: $parentpid\n";
print "ParentSub: My child pid is: $childpid\n";
}
}
}
sub child {
while ($line = <READPIPE>){
print "ChildSub: The child pid is: " $childpid,"\n";
print "ChildSub: I got this line from the pipe: ",$line," and the child pid is ",$childpid,"\n";
}
}
Current output is:
Hello World
ParentSub: The parent pid is: 2633 and the message being received is: Hello World
ParentSub: My parent pid is: 2633
ParentSub: My child pid is: 2634
ChildSub: The child pid is: 0
ChildSub: I got this line from the pipe: Hello World
, and the child pid is 0
I am running into a few issues that I don't quite understand.
It does not exit the loop properly- it exits but continues on with no context (there a 4 blank lines after the bottom of the output from hitting the enter key (aka "\n")- it doesn't proceed to the print "parent: the child pid with ......exit;
When I add a line in before the <STDIN>
ex. print "Enter something here: "
it doesn't repeat that line as part of the loop- making readability not flush.
Thanks for the information.
The main problem here is with closing the pipe ends. The direct error is about WRITEPIPE
.
All pipes' ends must be closed in all processes that see them. Otherwise something may be left waiting on a pipe which the other end forgot about. From Avoiding pipe deadlocks in perlipc
Whenever you have more than one subprocess, you must be careful that each closes whichever half of any pipes created for interprocess communication it is not using. This is because any child process reading from the pipe and expecting an EOF will never receive it, and therefore never exit. A single process closing a pipe is not enough to close it; the last process with the pipe open must close it for it to read EOF.
In this case the child closes WRITEPIPE
only after it reads (what parent writes), so the parent doesn't have the EOF on its WRITEPIPE
when needed and thus never quits it. You need Ctrl+C
to stop. Simply moving close WRITEPIPE
before child(...)
gets the program to work as intended.
It is also striking that the child never closes READPIPE
(and never exits).
There are other issues, some of stylistic nature, and a few compilation errors. Here is a working program, with as little change as feasible, with same names and simplified prints
use warnings;
use strict;
#use IO::Handle; # need this on pre-v5.14 (?) versions for autoflush
pipe (my $readpipe, my $writepipe);
$writepipe->autoflush(1);
STDOUT->autoflush(1);
my $parentpid = $$;
my $childpid = fork() // die "Fork failed $!\n";
# This is the child
if ($childpid == 0) {
close $writepipe; # child reads
child ($parentpid);
close $readpipe;
exit;
}
# This is the parent
close $readpipe; # parent writes
parent ($childpid);
close $writepipe;
waitpid $childpid, 0;
print "Parent: child $childpid exited, parent $parentpid done.\n";
sub parent {
while (1) {
my $input = <STDIN>;
if ($input eq "\n") { print "Undef- CRTL+C = Quit\n", last; }
else {
print "ParentSub: parent $parentpid got the message: ", $input;
print $writepipe "$input";
print "ParentSub: parent pid is $parentpid\n";
print "ParentSub: child pid is $childpid\n";
}
}
}
sub child {
while (my $line = <$readpipe>) {
print "ChildSub: child pid is $childpid\n";
print "ChildSub: got from the pipe: $line";
}
}
Prints
hello [Entered at STDIN]
ParentSub: parent 22756 got the message: hello
ParentSub: parent pid is 22756
ParentSub: child pid is 22757
ChildSub: child pid is 0
ChildSub: got from the pipe: hello
Parent: child 22757 exited, parent 22756 done. [After Enter at STDIN]
Note: the message in if ($input eq "\n")
is only good for the next run, since you also have last
there, to quit STDIN
on emtpy line. Usage messages belong on top, when the show starts.
I've simplified prints to be more readable. Copy yours back if you don't like these.
Comments
Aways first close the unneeded end of pipe in each process
Use lexical filehandles, they are better (my $reader
instead of READER
etc)
No need to test defined
on child since you nicely used defined-or (//
) at fork
The one needed test is normally for child, if ($pid == 0)
The child normally exit
s, so only the parent process remains after the child's block
With any writes/reads between processes you want to handle SIGPIPE
. See docs below
You need to check exit status, errors, etc. See docs below.
Documentation: perlipc, fork and exec, perlvar (on $?
, $!
, and %SIG
), pipe and links.
It is compulsory to carefully go through many parts of perlipc (along with other docs).