I noticed in my program that an exception raised from IO::Pipe was behaving oddly, and I cannot figure out what it's doing (let alone how it's doing it). I've boiled it down to a simple example program:
use strict;
use warnings;
use Carp;
use IO::Pipe;
my($path) = shift;
my($bad) = shift || "";
eval {
if ($path =~ m{pipe}i) {
my($bin) = ($bad ? "/bin/lsddd" : "/bin/ls");
my($pipe) = IO::Pipe->new();
$pipe->reader("$bin -l .");
print "$_" while <$pipe>;
$pipe->close;
}
elsif ($path =~ m{croak}i) {
croak "CROAKED" if $bad;
}
else {
die "DIED" if $bad;
}
};
if ($@) {
my($msg) = $@;
die "Caught Exception: $msg\n";
}
die "Uh-oh\n" if $bad;
print "Made it!\n";
The example program takes two arguments, one to indicate which code path to go down inside the eval
block, and the second to indicate whether or not to generate an error (anything that evaluates to false will not generate an error). All three paths behave as expected when no error is requested; they all print Made it!
with no error messages.
When asking for an error and running through the croak
or die
paths, it also behaves as I expect: the exception is caught, reported, and the program terminates.
$ perl example.pl die foo
Caught Exception: DIED at example.pl line 23.
and
$ perl example.pl croak foo
Caught Exception: CROAKED at example.pl line 11.
eval {...} called at example.pl line 10
When I send an error down the IO::Pipe path, though, it reports an error, but the program execution continues until the outer die
is reached:
$ perl example.pl pipe foo
Caught Exception: IO::Pipe: Cannot exec: No such file or directory at example.pl line 15.
Uh-oh
The first question is why -- why does the program report the "Caught Exception" message but not terminate? The second question is how do I prevent this from happening? I want the program to stop executing if the program can't be run.
There are two processes running after the eval
in the case of interest. You can see this by adding a print statement before if ($@)
. One drops through eval
and thus gets to the last die
.
The reader
forks when used with an argument, to open a process. That process is exec
-ed in the child while the parent returns, with its pid. The code for this is in _doit
internal subroutine
When this fails the child croak
s with the message you get. But the parent returns regardless as it has no IPC with the child, which is expected to just disappear via exec
. So the parent escapes and makes its way down the eval
. That process has no $@
and bypasses if ($@)
.
It appears that this is a hole in error handling, in the case when reader
is used to open a process.
There are ways to tackle this. The $pipe
is an IO::Handle and we can check it and exit that extra process if it's bad (but simple $pipe->error
turns out to be the same in both cases). Or, since close is involved, we can go to $?
which is indeed non-zero when error happens
# ...
$pipe->close;
exit if $? != 0;
(or rather first examine it). This is still a "fix," which may not always work. Other ways to probe the $pipe
, or to find PID of the escapee, are a bit obscure (or worse, digging into class internals).
On the other hand, a simple way to collect the output and exit code from a program is to use a module for that. A nice pick is Capture::Tiny. There are others, like IPC::Run and IPC::Run3, or core but rather low-level IPC::Open3.
Given the clarifications, the normal open
should also be adequate.