My goal is to launch an ongoing process (such as iostat) and parse some information. Once I am done, I would like to kill iostat and gracefully close the pipe. Remember, iostat will run forever until I kill it.
If I try to kill the process before closing the pipe, close() returns a -1 for 'no children'. If I don't kill the process before closing the pipe, it returns 13 because iostat is still trying to write to my pipe. In other words, this script will always die().
How do I close this pipe gracefully?
use warnings;
use strict;
my $cmd = "iostat 1";
my $pid = open(my $pipe, "$cmd |") || die "ERROR: Cannot open pipe to iostat process: $!\n";
my $count = 0;
while (<$pipe>){
if ($count > 2){
kill(9, $pid); # if I execute these two lines, close() returns -1
waitpid($pid, 0); # otherwise it returns 13
last;
}
$count++;
}
close($pipe) || die "ERROR: Cannot close pipe to iostat process: $! $?\n";
exit 0;
To understand why this happens, you need to understand what is going on in perl behind the scenes when you close such a pipe.
See also the description of close on perldoc where it says
If the filehandle came from a piped open, close returns false if one of the other syscalls involved fails or if its program exits with non-zero status.
After actually closing the underlying file descriptor, perl calls the system call 'waitpid()' to harvest the process (if it did not do that, you would have a 'zombie' process on your hands). If the process has already exited, then waitpid returns with the error code ECHILD, "No child processes". This is the error code which is reported by perl from the close function.
You can avoid this by removing the 'waitpid' call from inside your loop, so that perl can do it when you close the pipe. You will still get an error though, because the return code from the killed process consists of the signal number that terminated the process or'ed with the actual return code shifted left by 8 bits. You can handle that by checking that $! == 0 and $? == 9 (or whatever signal number you used).
So your code might look like this:
use warnings;
use strict;
my $cmd = "iostat 1";
my $pid = open(my $pipe, "$cmd |") || die "ERROR: Cannot open pipe to iostat process: $!\n";
my $count = 0;
while (<$pipe>){
if ($count > 2){
kill(9, $pid); # if I execute these two lines, close() returns -1
last;
}
$count++;
}
unless (close($pipe)) {
die "ERROR: Cannot close pipe to iostat process: $! $?\n" if $! != 0 || $? != 9;
}
exit 0;