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