Windows和Linux中的Perl超时命令

5
我正在编写一个perl脚本,需要在Windows和Linux中运行,它将运行一个进程,如果时间太长就超时,假设没有超时则返回退出代码,并在假设退出代码为零且未超时的情况下返回stdout。我不需要STDIN或STDERR。我尝试使用IPC::run,但无法使其正常工作。最接近成功的是使用IPC::Open3和waitpid($pid, WNOHANG)。但我遇到了问题。在Windows和Linux上看到了不同的结果。在下面的代码中,我给open3一个将失败的命令(ping没有任何参数“-z”)。在Linux上,代码立即返回负的退出代码。在Windows上,命令会超时。在Windows命令行上运行ping google.com -z会立即返回告诉我没有这样的参数。为什么waitpid返回零?
use strict;
use warnings;
use POSIX ":sys_wait_h";
use IPC::Open3;

my $inFH;
my $outFH;
my @cmd = ("ping", "google.com", "-z");
my $pid = open3( $inFH, $outFH, 0, @cmd);
my $counter=0;
my $waitret;
my $exitcode;
do{
    $counter++;
    $waitret = waitpid($pid,WNOHANG);   
    $exitcode = $?;
}while( !$waitret and ($counter < 4_000_000));

if ($counter >= 4_000_000) {
    print "Command timed out\n";
    kill(9, $pid);
} else {
    print "Exit Code: $exitcode\n";
}

欢迎提供其他解决方案。我使用nohang选项的waitpid函数的唯一原因是如果进程运行时间过长,需要杀死该进程。在此期间,我没有其他处理需求。 我使用的是Windows 10 Strawberry Perl 5.28便携版。我的Debian机器上安装了Perl 5.24。


1
请查看IPC::Cmd,它有一个超时参数,因此您可以避免繁忙的等待循环。还可以参考Timeout for launching a bash command in perl - Håkon Hægland
使用IPC::Cmd运行我的脚本似乎会耐心地等待进程结束,然后通知我是否违反了超时时间。使用'ping google.com /t'运行会导致运行挂起等待ping停止,即使我设置了超时时间为4。在Linux中,它似乎按预期工作。 - serpixo
不幸的是,Windows没有真正的forking功能,因此需要通过ithreads进行模拟。Windows也不支持POSIX信号。这可能就是你看到不同行为的原因。 - Grinnz
1个回答

3
The IPC::Run支持各种超时和定时器, 在Win32上也应该可以工作。
use warnings;
use strict;
use feature 'say';

use IPC::Run qw(run timeout);

my $out;

eval {
    run [ qw(sleep 20) ], \undef, \$out, timeout(2) or die "Can't run: $?"
};  
if ($@) { 
    die $@ if $@ !~ /^IPC::Run: timeout/;
    say "Eval: $@";
}
timeout会抛出异常,eval也一样。还有其他计时器,其行为更加微妙和可管理。请参阅文档。
如果捕获的异常不是由IPC::Run的计时器引起的,那么异常将被重新抛出--通过模块在我的系统上打印的消息判断,该字符串以IPC::Run: timeout开头。请检查您系统上的情况。
虽然某些东西在Windows上无法工作,但我认为计时器应该可以。我现在无法测试。
据报道,在上述工作中,如果有一个更有意义的命令,则在超时时会发出SIGBREAK (21)信号,并且一旦处理该信号,进程将保留下来。
在这种情况下,请在处理程序中手动终止它。
use warnings;
use strict;
use feature 'say';
use IPC::Run qw(run harness timeout);

my $out;
my @cmd = qw(sleep 20);

my $h = harness \@cmd, \undef, \$out, timeout(2);

HANDLE_RUN: {
    local $SIG{BREAK} = sub {
        say "Got $_[0]. Terminate IPC::Run's process";
        $h->kill_kill;
    };  

    eval { run $h };  
    if ($@) { 
        die $@ if $@ !~ /^IPC::Run: timeout/;
        say "Eval: $@";
    }
};

为了能够在此处使用模块的kill_kill,我首先创建了测试工具,然后在安装处理程序时可以使用该工具,并且在触发时可以调用kill_kill
另一种方法是使用Win32::Process::InfoWin32::Process::List查找进程ID,并使用killWin32::ProcessTASKKILL终止它。请参见this post(后半部分)。

添加块仅用于将信号处理程序“本地化”。在实际代码中,所有这些可能以某种方式作用域化,因此可能不需要额外的块。

请注意,Windows没有POSIX信号,并且Perl仅模拟一些非常基本的UNIX信号,以及特定于Windows的SIGBREAK


那个...有点奇怪...如果我只是使用你的代码,将sleep 20替换为ping google.com /t,那么我会在提示符上收到以下消息:"Terminating on signal SIGBREAK(21)",我的脚本完全死了。如果我为$SIG{BREAK}添加一个catch,那么我会在我设置的超时期间看到我的SIG{BREAK}消息...但是脚本仍然会在至少另外20秒的ping上挂起...也许它正在尝试以某种方式优雅地终止命令?我能让它更强制吗? - serpixo
@serpixo 嘿...在发布之前我已经搜索过了(现在无法在Win上测试),并且有一些关于SIGBREAK的参考资料——但只涉及模块_安装_。还有其他计时方法,并且文档链接部分提供了完整的示例。你能去那里看看吗?我现在就是无法在Windows上测试 :( - zdim
@serpixo 如果你能捕获它,那么几乎肯定可以操纵它。 我会尝试这样做:在“信号”处理程序中使用kill_kill 命令(在Win32上没有真正的信号,只有基本信号的仿真)。 当然,最好不要这样做,可以从文档中尝试其他方法来计时。 - zdim
谢谢zdim,如果我找到更好的解决方案,我会再联系你的。目前你的解决方案是最适合我的需求的。希望我能找到一个更及时的解决方案。 - serpixo
@serpixo 好的,我会再看一下并尝试在Windows上测试。如果仍然需要手动终止处理程序,请告诉我,我可以添加代码来解决这个问题。 - zdim
@serpixo 添加了一个从处理程序中停止进程的方法。仍然无法在Windows上测试(我的虚拟机崩溃了)... :( 在Linux上使用$SIG{ INT }进行测试。 - zdim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接