为什么 Perl 的 IO::Pipe 异常的行为与 eval 块中的 croak 或 die 不同?

Why does Perl's IO::Pipe exception behave differently than croak or die in eval block?

我注意到在我的程序中,从 IO::Pipe 引发的异常行为异常,我无法弄清楚它在做什么(更不用说它是如何做的了)。我把它归结为一个简单的示例程序:

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";

示例程序有两个参数,一个指示在 eval 块中执行哪个代码路径,第二个指示是否生成错误(任何计算结果为 false 的都不会产生错误)。当没有请求错误时,所有三个路径都按预期运行;他们都打印 Made it! 而没有错误消息。

当通过 croakdie 路径询问错误并 运行ning 时,它的行为也如我所料:异常被捕获、报告,程序终止.

$ perl example.pl die foo
Caught Exception: DIED at example.pl line 23.

$ perl example.pl croak foo
Caught Exception: CROAKED at example.pl line 11.
    eval {...} called at example.pl line 10

当我在 IO::Pipe 路径下发送错误时,它会报告错误,但程序会继续执行,直到到达外部 die

$ perl example.pl pipe foo
Caught Exception: IO::Pipe: Cannot exec: No such file or directory at example.pl line 15.

Uh-oh

第一个问题是 - 为什么程序报告 "Caught Exception" 消息但不终止?第二个问题是如何防止这种情况发生?如果程序不能 运行.

我希望程序停止执行

在感兴趣的eval之后有两个过程运行。您可以通过在 if ($@) 之前添加打印语句来查看这一点。一个通过 eval 下降,从而到达最后一个 die.

reader 与参数一起使用时会分叉,以打开进程。该进程在 child 中被 exec 编辑,而 parent returns 中带有其 pid。代码在 _doit internal subroutine

当此操作失败时,child croak 会显示您收到的消息。但是 parent returns 无论如何,因为它与 child 没有 IPC,预计会通过 exec 消失。所以 parent 逃脱并沿着 eval 前进。该进程没有 $@ 并绕过 if ($@).

这似乎是错误处理中的一个漏洞,在 reader 用于打开进程的情况下。

有很多方法可以解决这个问题。 $pipe 是一个 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 涉及,我们可以去 $? 这确实是 non-zero 当错误发生时

# ...
$pipe->close;
exit if $? != 0;

(或者更确切地说,首先检查它)。这仍然是一个 "fix,",可能并不总是有效。其他探测 $pipe 或查找逃逸者 PID 的方法有点晦涩(或者更糟的是,挖掘 class 内部结构)。

另一方面,从程序中收集输出和退出代码的一种简单方法是为此使用一个模块。一个不错的选择是 Capture::Tiny. There are others, like IPC::Run and IPC::Run3, or core but rather low-level IPC::Open3.

鉴于说明,正常的 open 也应该足够了。