2

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;
sandro
  • 55
  • 6

2 Answers2

3

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;
harmic
  • 28,606
  • 5
  • 67
  • 91
  • This makes sense and will work, but what about the possibility of the pipe being killed with signal 9 by another process? In other words: How do I know the child process was killed by me? – sandro Nov 20 '13 at 15:45
  • If you want to detect if your process was killed by you and not someone else, then I would capture the return value of 'kill' in a variable, and check that variable at the end. Kill returns the number of processes actually signalled so it would be 0 if someone else got there first. – harmic Nov 21 '13 at 00:08
  • BTW: I would recommend using something a little less drastic than signal 9. If the child process needs to do any clean up it will not be able to if you use 9, since it cannot be trapped. Signal 2 is usually better. – harmic Nov 21 '13 at 00:09
1

I would rely on perl to do all the housekeeping that is necessary when the program completes. The close is only critical if you are likely to run out of file handles, or if you want to make sure no data is lost on an output handle.

I would also use the three-parameter from of open, as it is recommended practice.

Like this

use strict;
use warnings;

my $cmd   = 'iostat 1';
my $pid   = open my $pipe, '-|', $cmd or die "ERROR: Cannot open pipe from command: $!";
my $count = 0;

while (<$pipe>){
  last if $count > 2;
  $count++;
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • But isn't it good practice to explicitly close handles? I understand perl will clean up after me, but I feel it is always good to be explicit just in case. – sandro Nov 20 '13 at 15:38
  • There really is no point in closing input handles when you are ignoring any errors that may come back from `readline` (the same as `<>`). If you consider it safe to assume that any error is an end-of-file then you are rather too late to do anything about any problems at the `close`. Except, as I said, if you have many input files open at once then it may be useful to close them as they become free. – Borodin Nov 20 '13 at 15:46
  • If you are *really* concerned about getting errors in the output from `iostat` then you should code it so that you start to process a block of output only when you see the header from a previous one. Any error in the meantime will cause `readline` to fail, and if you didn't kill the process yourself then either someone else did or something nasty has happened. In general I don't think it will matter exactly what, as long as you know that the data you *have* read is complete and correct. – Borodin Nov 20 '13 at 16:03
  • Yes, I do this in my actual code (I think you meant process the previous block of output when you see the header for the next block, correct?). – sandro Nov 20 '13 at 21:37
  • @sandro: Yes I did. I guess I got my thoughts wrapped around my words! So all you have to do is to handle errors when you expect some data but the `readline` fails. When you're no longer interested in hearing from `iostat` then just exit. Perl will kill the child process and close your file handle. – Borodin Nov 20 '13 at 22:09