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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With