Perl timeout command in windows and linux

随声附和 提交于 2020-02-03 07:30:49

问题


I'm writing a perl script that needs to work in windows and linux that will run a process, timeout if it takes too long, return the exit code assuming it didn't timeout, and return stdout assuming the exitcode was zero and it didn't timeout. I don't need STDIN or STDERR. I've tried to use IPC::run but couldn't get it to work.

The closest I got is with IPC::Open3 and waitpid($pid, WNOHANG). But I've hit a snag. I'm seeing different results on windows and linux. In the code below I give open3 a command that will fail (ping doesn't have any argument -z). On linux the code immediately returns a negative exit code. On windows the command times out. Running ping google.com -z on the windows command line immediately returns telling me there is no such argument. Why does ``waitpid` return a zero?

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

Other solutions are welcome. The only reason I use waitpid with nohang is to kill the process if it takes too long. I dont have any other processing I need to do in the meantime. I'm using windows 10 strawberry perl 5.28 portable on windows. My debian machine has perl 5.24.


回答1:


The IPC::Run has support for various timeouts and timers, which should also work on Win32.

Basic:

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: $@";
}

The timeout throws an exception, thus the eval. There are other timers, with behavior which is more subtle and manageable. Please see documentation.

The exception is re-thrown if the one caught isn't caused by IPC::Run's timer -- judged by what the module's version on my system prints in the message, a string starting with IPC::Run: timeout. Please check what that is on your system.

While some things don't work on Windows the timers should, I think. I can't test right now.


It's reported that, while the above works, with a more meaningful command in place the SIGBREAK (21) is emitted at timeout, and once it is handled the process stays around.

In this case terminate it manually in the handler

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: $@";
    }
};

In order to be able to use the module's kill_kill here I first create the harness, which is then available when the handler is installed, and on which kill_kill can be called when it fires.

Another way would be to find the process ID (with Win32::Process::Info or Win32::Process::List), and terminate it with kill, or Win32::Process, or TASKKILL. See this post (second half).

A block is added only to local-ize the signal handler. In real code all this is likely scoped in some way so an extra block may not be needed.

Note that Windows doesn't have POSIX signals and that Perl emulates only a few very basic UNIX signals, along with the Windows-specific SIGBREAK.



来源:https://stackoverflow.com/questions/53770493/perl-timeout-command-in-windows-and-linux

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!