【问题标题】:Perl timeout command in windows and linuxWindows 和 linux 中的 Perl 超时命令
【发布时间】:2019-05-15 04:18:42
【问题描述】:

我正在编写一个需要在 windows 和 linux 中运行的 perl 脚本,它将运行一个进程,如果花费的时间太长,则超时,假设它没有超时,则返回退出代码,并假设退出代码为零返回标准输出它没有超时。我不需要 STDIN 或 STDERR。我尝试使用IPC::run,但无法使用。

我得到的最接近的是IPC::Open3waitpid($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 上使用 windows 10 草莓 perl 5.28 便携版。我的 debian 机器有 perl 5.24。

【问题讨论】:

  • IPC::Cmd它有一个超时参数,这样可以避免忙等待循环。另请参阅Timeout for launching a bash command in perl
  • 使用 IPC::Cmd 并运行我的脚本似乎耐心地等待进程结束,然后通知我是否违反了超时。使用 'ping google.com /t' 运行会导致运行挂起,等待 ping 停止,即使我将超时设置为 4。在 linux 中它似乎按预期工作。
  • 不幸的是,Windows 没有真正的分叉,所以它是通过 ithreads 模拟的。 Windows 也不支持 POSIX 信号。这可能就是您看到不同行为的原因。

标签: perl timeout waitpid


【解决方案1】:

IPC::Run 支持各种timeouts and timers,应该也可以在 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: 开头的字符串:运行:超时。请检查您的系统上的内容。

虽然有些东西在 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,我首先创建了harness,它在安装handler时可用,并且可以在触发时调用kill_kill

另一种方法是查找进程 ID(使用 Win32::Process::InfoWin32::Process::List), 并以killWin32::ProcessTASKKILL 终止它。见this post(下半场)。

仅将块添加到local-ize 信号处理程序。在实际代码中,所有这些都可能以某种方式限定范围,因此可能不需要额外的块。

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

【讨论】:

  • 这行得通....奇怪...如果我只是使用您的代码并将 sleep 20 替换为 ping google.com /t 那么我会在提示符下收到以下消息:“终止信号 SIGBREAK( 21)”,我的脚本完全死了。如果它为 $SIG{BREAK} 设置了一个捕获,那么我会在我输入的超时期间看到我的 SIG{BREAK} 消息....但是脚本仍然在 ping 上挂起至少 20 秒... . 也许它试图以某种方式优雅地终止命令?我可以让它更有力吗?
  • @serpixo heh ...我在发布之前已经搜索过(现在无法在 Win 上测试),并且有一些对 SIGBREAK 的引用——但仅限于模块 安装。还有其他计时方法,以及文档链接部分的完整示例。你能看看那里吗?我现在无法在 Windows 上进行测试 :(
  • @serpixo 如果你能抓住它,那么你几乎可以肯定地操纵它。我会试试这个:kill_kill“信号”处理程序中的那个命令(Win32 上没有真正的信号,但只有基本信号的模拟)。当然,最好不必做所有这些;从文档中尝试其他其他方式。
  • 谢谢zdim,如果我找到更好的方法,我会回复你。目前,您的解决方案最适合我的需求。希望我能找到更...及时的解决方案。
  • @serpixo 好的。稍后我会多看一些并尝试在 Windows 上进行测试。如果它停留在从处理程序中手动终止进程,请告诉我,我可以为此添加代码。
猜你喜欢
  • 2020-12-24
  • 2014-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-01
  • 1970-01-01
  • 2015-12-23
  • 2018-04-17
相关资源
最近更新 更多