App-ptimeout
view release on metacpan or search on metacpan
lib/App/ptimeout.pm view on Meta::CPAN
package App::ptimeout;
use strict;
use warnings;
no warnings 'numeric';
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
use POSIX qw(WNOHANG);
use Proc::ProcessTable;
our $VERSION = '1.0.3';
sub _run {
my($timeout, @argv) = @_;
if($timeout =~ /m$/) { $timeout *= 60 }
elsif($timeout =~ /h$/) { $timeout *= 3600 }
pipe(my $stderr_reader, my $stderr_writer)
or die("Error creating stderr pipe\n");
_set_nonblocking($stderr_reader);
my $pid = fork();
if(!defined($pid)) {
die("Error forking\n")
} elsif(!$pid) {
# the child process, which runs the command
# only the parent cares about this end of the pipe, so close the copy in this process
close $stderr_reader or die("Error closing stderr reader\n");
# re-open STDERR as a duplicate of the pipe we inherited from the parent
open STDERR, '>&', $stderr_writer
or die("Error redirecting stderr\n");
# now that we've duplicated it we don't need another copy
close $stderr_writer or die("Error closing stderr writer\n");
my $status = system @argv;
exit _normalise_status($status);
}
# only the parent process, the watchdog, gets here
# only the child cares about this end of the pipe, it now has a copy, so close ours
close $stderr_writer or die("Error closing stderr writer\n");
my $deadline = time + $timeout;
my $timed_out = 0;
my $child_status;
my $stderr_buffer = '';
my $held_line;
# Loop while the process we're monitoring does its thang
while(1) {
# The background process is running, and its STDERR is being captured.
# If it's said anything on its STDERR, flush it to the *real* STDERR.
_pump_stderr(
$stderr_reader,
\$stderr_buffer,
\$held_line,
timed_out => $timed_out,
);
# Has the process finished?
my $waited = waitpid($pid, WNOHANG);
if($waited == $pid) { # yes, and it exited
$child_status = $?;
last;
}
if($waited == -1) { # it's all gone wrong
die("Error waiting for child process\n");
}
if(time >= $deadline) { # still running, but been running too long
# looks like dpulicate code, but it avoids a race condition
$waited = waitpid($pid, WNOHANG);
if($waited == $pid) {
$child_status = $?;
last;
}
warn "timed out\n";
$timed_out = 1;
$child_status = _terminate_process_tree($pid);
last;
}
select undef, undef, undef, 0.1;
}
_flush_remaining_stderr(
$stderr_reader,
\$stderr_buffer,
\$held_line,
timed_out => $timed_out,
);
close $stderr_reader or die("Error closing stderr reader\n");
exit($timed_out ? 124 : _normalise_status($child_status));
}
sub _normalise_status {
my($status) = @_;
return 255 if($status == -1);
return $status >> 8;
}
sub _set_nonblocking {
my($fh) = @_;
my $flags = fcntl($fh, F_GETFL, 0);
defined($flags) or die("Error reading stderr flags\n");
fcntl($fh, F_SETFL, $flags | O_NONBLOCK)
or die("Error setting stderr nonblocking\n");
}
( run in 2.562 seconds using v1.01-cache-2.11-cpan-f56aa216473 )