IPC::Run - 检测过早的 child 退出和关闭的管道

IPC::Run - Detection of premature child exit and closed pipes

我想使用 IPC::Run 通过 child 的 STDIN、STDOUT 和 STDERR(启动、泵、完成)与 child 通信。好像还行。

我想知道如何检测

更新:感谢@zdim提醒我检查SIGPIPE信号。这是我的答案的更新,它还检查 SIGPIPE:

我使用 startpumpfinish 做了一个简单的测试。这是我使用的主要脚本p.pl

use feature qw(say);
use strict;
use warnings;
use IPC::Run;

my $child_in;
my $child_out;
my $child_err;
my $child_name = shift;

my $harness = eval {
    IPC::Run::start [ $child_name ], $child_in, $child_out, $child_err;
};
if ( $@ ) {
    chomp $@;
    die "Caught exception: '$@'";
}
for (1..2) {
    $child_in = "Joe$_\n";
    say "Parent sleeping for 1 second..";
    sleep 1;
    eval {
        local $SIG{PIPE} = sub { 
            die "Parent received SIGPIPE. "
              . "Child is either dead or has closed its input pipe\n";
        };
        say "Sending data to child..";
        my $result = $harness->pump;
        say "IPC::Run::pump() returned: ", $result ? "TRUE" : "FALSE";
    };
    if ( $@ ) {
        chomp $@;
        say "IPC::Run::pump() failed: '$@'";
        last;
    }
    say "$child_in = '$child_in'";
    say "$child_out = '$child_out'";
}
say "Finishing harness..";
my $res = eval {
    local $SIG{PIPE} = sub { 
        die "Parent received SIGPIPE. "
          . "Child is either dead or has closed its input pipe\n";
    };
    $harness->finish;
};
if ( $@ ) {
    chomp $@;
    die "IPC::Run::finish() failed: '$@'\n";
}
printf "IPC::Run::finish() returned: '%s'\n", $res ? "TRUE" : "FALSE";
chomp $child_out;
say "STDOUT from child: '$child_out'";
chomp $child_err;
say "STDERR from child: '$child_err'";
say "Child returned exit code: ", $harness->result;
say "Parent exited normally.."

我使用了三种不同的 child 脚本:

child.pl:

#! /usr/bin/env perl    
use feature qw(say);
use strict;
use warnings;

my $reply = <STDIN>;
chomp $reply;
say "Hello $reply";
my $reply2 = <STDIN>;
chomp $reply2;
say "Got second reply: $reply2";
exit 0;

并输出:

$ p.pl child.pl
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() returned: TRUE
$child_in = ''
$child_out = ''
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() returned: TRUE
$child_in = ''
$child_out = ''
Finishing harness..
IPC::Run::finish() returned: 'TRUE'
STDOUT from child: 'Hello Joe1
Got second reply: Joe2'
STDERR from child: ''
Child returned exit code: 
Parent exited normally..

child2.pl:

#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;

my $reply = <STDIN>;
chomp $reply;
say "Hello $reply";
die "Child exception\n";

并输出:

$ p.pl child2.pl
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() returned: TRUE
$child_in = ''
$child_out = ''
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() failed: 'Parent received SIGPIPE. Child is either dead or has closed its input pipe'
Finishing harness..
IPC::Run::finish() failed: 'Parent received SIGPIPE. Child is either dead or has closed its input pipe'

child3.pl:

#! /usr/bin/env perl
use strict;
use warnings;

close \*STDIN;
close \*STDOUT;
close \*STDERR;
sleep 5;
exit 2;

并输出:

$ p.pl child3.pl 
Parent sleeping for 1 second..
Sending data to child..
IPC::Run::pump() failed: 'ack Parent received SIGPIPE. Child is either dead or has closed its input pipe'
Finishing harness..
IPC::Run::finish() failed: 'Parent received SIGPIPE. Child is either dead or has closed its input pipe'

所以对于这些测试,似乎 SIGPIPE 信号可以用来检查 child 是否存活或是否关闭了它的输入管道。请注意,如果您尝试在 child 退出后调用 pump(),则 child 的先前输出将丢失,请参阅 child2.pl 示例。

pump 抛出 die 错误,或将其消息写入 STDERR 如果“在所有利用的活动完成后调用 ”请参阅 ROUTINES 部分之前和 pump 本身。如果 child 退出,就会出现第二种情况。因此,将 pump 调用包装在 eval 中,并将警告转换为 die 以捕获这两种情况

if ($talk_to_child) 
{
    eval {
        local $SIG{__WARN__} = sub { die "pump WARNING: @_" };
        pump $harness;
    };
    if ($@) { 
        print $@;
        $talk_to_child = 0;
    }; 
}
# ... and eval {} for finish()

但仅此一项并不能解决问题:当 parent 尝试写入已退出的 child 时,它会得到一个 SIGPIPE,这会彻底终止进程。当 child 关闭流并且 parent 尝试写入时也是如此。因此还要为 SIGPIPE

安装信号处理程序
$SIG{PIPE} = sub { 
    say "$_[0]: $!";
    $talk_to_child = 0;  # global
};

这样 parent 在 SIGPIPE 之后仍然存在。通过执行 local $SIG{PIPE} = ... 来考虑 local-izing the change to the global %SIG,即使只是在一般原则上也是一种很好的做法。另一方面,全局处理一个可以突然终止你的信号是很有意义的(即使在处理程序可能决定退出的情况下)。

即使处理了 $SIG{PIPE},仍然需要 eval,因为 pump 也会抛出。

这些一起处理了我想出的所有测试,实际上就像它们一样。尽管如此,如果需要,仍需要在处理程序和 eval 中进行一些处理以区分感兴趣的情况。

如果这加起来太多了,另一种方法是在每次调用之前进行检查。请参阅 以了解 one-line 检查(包含在 subs 中):(1) child 是否为 运行,是否使用 result,以及 (2) 是否“ 有打开的 I/O 通道或活动进程",使用 pumpable

我认为您两者都想要,并且还要放入 SIGPIPE 处理程序。这应该涵盖它。

我不能在这里更具体,因为问题没有提供具体细节。