use run_limited; # I need a better name
my $runner = Runner->new; # got to get the names straight
$runner->load('true');
$runner->load('sleep 2');
$runner->run("$timeout");
####
$VAR1 = \bless( {
'sleep 999' => {
'pid' => '38864',
'stderr' => bless( [], 'Thread::Queue' ),
'status' => 2,
'signaling' => bless( [
'command complete'
], 'Thread::Queue' ),
'error' => [],
'output' => [],
'stdout' => bless( [], 'Thread::Queue' )
},
'echo STDERR 1>&2' => {
'stderr' => bless( [], 'Thread::Queue' ),
'time' => 1,
'status' => 0,
'output' => [],
'outcome' => 'command complete',
'pid' => '57234',
'signaling' => bless( [], 'Thread::Queue' ),
'error' => [
'STDERR
'
],
'stdout' => bless( [], 'Thread::Queue' )
},
'no_such_command' => {
'stderr' => bless( [], 'Thread::Queue' ),
'time' => 1,
'status' => 0,
'output' => [],
'outcome' => 'command complete',
'pid' => '57236',
'signaling' => bless( [], 'Thread::Queue' ),
'error' => [],
'stdout' => bless( [], 'Thread::Queue' )
},
'true' => {
'stderr' => bless( [], 'Thread::Queue' ),
'time' => 1,
'status' => 0,
'output' => [],
'outcome' => 'command complete',
'pid' => '50508',
'signaling' => bless( [], 'Thread::Queue' ),
'error' => [],
'stdout' => bless( [], 'Thread::Queue' )
},
'echo STDOUT' => {
'stderr' => bless( [], 'Thread::Queue' ),
'time' => 1,
'status' => 0,
'output' => [
'STDOUT
'
],
'outcome' => 'command complete',
'pid' => '20080',
'signaling' => bless( [], 'Thread::Queue' ),
'error' => [],
'stdout' => bless( [], 'Thread::Queue' )
},
'sleep 2' => {
'stderr' => bless( [], 'Thread::Queue' ),
'time' => 3,
'status' => 0,
'output' => [],
'outcome' => 'command complete',
'pid' => '43846',
'signaling' => bless( [], 'Thread::Queue' ),
'error' => [],
'stdout' => bless( [], 'Thread::Queue' )
}
}, 'Runner' );
####
$stdout=$runner->stdout($command);
$stderr=$runner->stderr($command);
... etc ...
####
#!/usr/bin/perl
#
{
package Runner;
use strict;
use warnings;
use threads;
use Thread::Queue;
use IPC::Open3;
sub new {
my $class = shift;
return bless {}, $class;
}
sub load {
my $self=shift;
for (@_) {
$self->{$_}={};
}
}
sub run {
my $self=shift;
my $timeout=shift;
my $running_commands = 0;
my @threads;
local $SIG{CHLD}='IGNORE'; # incantation against zombies is this voodoo ?
while (my ($command, $info) = each %$self) {
$info->{stdout} = new Thread::Queue;
$info->{stderr} = new Thread::Queue;
$info->{signaling} = new Thread::Queue;
push @threads, threads->create(
\&RunInThread,
$command,
$info->{stdout},
$info->{stderr},
$info->{signaling},
);
$info->{output} = [];
$info->{error} = [];
$info->{status} = 2;
$info->{pid} = $info->{signaling}->dequeue;
$running_commands++;
}
for my $time (1..$timeout) {
last unless $running_commands;
sleep 1;
for my $info (values %$self) {
next unless ($info->{status})>>1;
while (my $result = $info->{signaling}->dequeue_nb) {
$info->{outcome}=$result;
if ($result eq "command complete") {
$info->{status}=0;
$info->{time}=$time;
$running_commands--;
next;
} else {
# something bad is being signalled
$info->{status}=1;
}
}
while (my $result = $info->{stdout}->dequeue_nb) {
push @{$info->{output}}, $result;
}
while (my $error = $info->{stderr}->dequeue_nb) {
push @{$info->{error}}, $error;
}
}
}
# time is up
# kill those that did not complete
my $killed=0;
if ($running_commands) {
for my $info (values %$self) {
next unless $info->{status} == 2;
$killed++;
# politeness costs nothing ?
kill 15, $info->{pid};
while (my $result = $info->{stdout}->dequeue_nb) {
if ($result eq "command complete") {
$info->{status}=0;
$info->{time}=$timeout;
next;
}
push @{$info->{output}}, $result;
}
kill 9, $info->{pid};
waitpid $info->{pid}, 0;
}
}
$_->join for @threads;
return $killed;
}
sub RunInThread {
my ( $cmd, $stdout, $stderr, $signaling) = @_;
my ( $in, $out, $err, $pid);
# duplicate stderr and stdout
open( $out, ">&STDOUT" ) or
die "Can't dup STDOUT to OUTPUT: $!\n";
open( $err, ">&STDERR" ) or
die "Can't dup STDERR to OUTERR: $!\n";
eval { $pid = open3($in, $out, $err, $cmd) };
if ($@) {
$signaling->enqueue($@);
exit 1; # this will be in a forked child
} else {
$signaling->enqueue($pid);
$stdout->enqueue( $_ ) while <$out>;
$stderr->enqueue( $_ ) while <$err>;
$signaling->enqueue("command complete");
}
}
# the following right bracket closes the package
}
1;
####
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Test::More tests => 4;
my $timeout = (shift or 5);
push @INC, '.';
BEGIN {use_ok('run_limited')}
my $runner = Runner->new;
isa_ok($runner, 'Runner');
can_ok($runner, qw(load run));
$runner->load('true');
$runner->load('sleep 2');
$runner->load('sleep 999');
$runner->load('echo STDOUT');
$runner->load('echo STDERR 1>&2');
$runner->load('no_such_command');
is ($runner->run("$timeout"), 1, "Check correct number had to be killed");
print Dumper(\$runner);
####
our $sigpipe=0;
$SIG{PIPE}=sub{$sigpipe++};