File Coverage

blib/lib/App/MultiModule/Tasks/Runner.pm
Criterion Covered Total %
statement 20 105 19.0
branch 0 42 0.0
condition 0 12 0.0
subroutine 7 15 46.6
pod 2 2 100.0
total 29 176 16.4


line stmt bran cond sub pod time code
1             package App::MultiModule::Tasks::Runner;
2             $App::MultiModule::Tasks::Runner::VERSION = '1.161240';
3 4     4   2068 use 5.006;
  4         16  
4 4     4   12 use strict;
  4         4  
  4         74  
5 4     4   10 use warnings FATAL => 'all';
  4         4  
  4         110  
6 4     4   14 use Data::Dumper;
  4         4  
  4         164  
7 4     4   12 use Storable;
  4         4  
  4         154  
8 4     4   16 use POE qw( Wheel::Run );
  4         4  
  4         26  
9              
10 4     4   62006 use parent 'App::MultiModule::Task';
  4         4  
  4         22  
11              
12             =head1 NAME
13              
14             App::MultiModule::Tasks::Runner - Run external programs under App::MultiModule
15              
16             =cut
17              
18             {
19             my $ps_cache;
20             my $cache_ts;
21             sub _is_running {
22 0 0   0     my $regex = shift or die 'is_running: regex required';
23 0 0         $cache_ts = 0 unless defined $cache_ts;
24 0           my $time = time;
25 0 0         if($time - 5 > $cache_ts) {
26 0           $cache_ts = $time;
27 0           undef $ps_cache;
28             }
29 0 0         if(not $ps_cache) {
30 0           $ps_cache = `ps -eo cmd`;
31             }
32 0           return $ps_cache =~ /$regex/;
33             }
34              
35             =head2 message
36              
37             =cut
38              
39             sub message {
40 0     0 1   my $self = shift;
41 0           my $message = shift;
42             # print STDERR 'MESSAGE: ' . Data::Dumper::Dumper $message;
43             # print STDERR "MESSSAGE\n";
44 0           my %args = @_;
45             $self->debug('message', message => $message)
46 0 0         if $self->{debug} > 5;
47 0           my $state = $self->{state};
48 0 0         $state->{running_progs} = {} unless $state->{running_progs};
49             my $prog = $message->{runner_program_prog}
50 0 0         or die 'run_program: runner_program_prog required';
51 0           my $prog_args = Storable::dclone($message->{runner_program_args});
52 0 0         $prog_args = [] unless $prog_args;
53 0 0 0       die 'run_program: runner_program_args must be an ARRAY or HASH reference'
      0        
54             if not ref $prog_args or
55             ( ref $prog_args ne 'ARRAY' and
56             ref $prog_args ne 'HASH');
57 0 0         if(ref $prog_args eq 'HASH') {
58 0           my @args = ();
59 0           my @sorted_arg_numbers = sort { $a <=> $b } keys %$prog_args;
  0            
60 0           foreach my $arg_number (@sorted_arg_numbers) {
61 0           push @args, $prog_args->{$arg_number};
62             }
63 0           $prog_args = \@args;
64             }
65 0           my $prog_run_key = "$prog," . join ',',@$prog_args;
66             my $prog_regex = $message->{runner_process_regex}
67 0 0         or die 'run_program: runner_process_regex required';
68             $message->{runner_return_type} = 'gather'
69 0 0         unless $message->{runner_return_type};
70 0           my $return_type = $message->{runner_return_type};
71 0 0 0       die 'run_program: runner_return_type must be one of "json", "gather"'
72             if $return_type ne 'json' and $return_type ne 'gather';
73 0 0         if($return_type eq 'gather') {
74 0           $message->{runner_stdout} = '';
75 0           $message->{runner_stderr} = '';
76             }
77              
78 0 0 0       if($state->{running_progs}->{prog_run_key} or _is_running($prog_regex)) {
79 0           $message->{runner_message_type} = 'already running';
80 0           $self->emit($message);
81 0           return;
82             }
83 0           undef $ps_cache;
84             my $on_start = sub {
85 0     0     my $child = POE::Wheel::Run->new(
86             Program => [ $prog, @$prog_args],
87             StdoutEvent => "got_child_stdout",
88             StderrEvent => "got_child_stderr",
89             CloseEvent => "got_child_close",
90             );
91              
92 0           $_[KERNEL]->sig_child($child->PID, "got_child_signal");
93 0           $_[HEAP]{children_by_wid}{$child->ID} = $child;
94 0           $_[HEAP]{children_by_pid}{$child->PID} = $child;
95 0           $message->{runner_start_time} = time;
96 0           $message->{runner_pid} = $child->PID;
97 0           $message->{runner_message_type} = 'start';
98 0           $message->{runner_prog_run_key} = $prog_run_key;
99 0           my $send_message = Storable::dclone($message);
100 0           $self->emit($send_message);
101 0           delete $message->{runner_message_type};
102 0           $state->{running_progs}->{$prog_run_key} = $message;
103 0           };
104             # Wheel event, including the wheel's ID.
105             my $on_child_stdout = sub {
106 0     0     my ($stdout_line, $wheel_id) = @_[ARG0, ARG1];
107 0           my $child = $_[HEAP]{children_by_wid}{$wheel_id};
108 0 0         if($return_type eq 'gather') { #simply gather all of
109 0           $message->{runner_stdout} .= "$stdout_line\n";
110             }
111 0           print "pid ", $child->PID, " name '$prog_args->[0]' STDOUT: $stdout_line\n";
112 0           };
113              
114             # Wheel event, including the wheel's ID.
115             my $on_child_stderr = sub {
116 0     0     my ($stderr_line, $wheel_id) = @_[ARG0, ARG1];
117 0           my $child = $_[HEAP]{children_by_wid}{$wheel_id};
118 0 0         if($return_type eq 'gather') { #simply gather all of
119 0           $message->{runner_stderr} .= "$stderr_line\n";
120             }
121 0           print "pid ", $child->PID, " name '$prog_args->[0]' STDERR: $stderr_line\n";
122 0           };
123              
124             # Wheel event, including the wheel's ID.
125             my $on_child_close = sub {
126 0     0     my $wheel_id = $_[ARG0];
127 0           my $child = delete $_[HEAP]{children_by_wid}{$wheel_id};
128              
129 0           undef $ps_cache;
130 0 0         unless (defined $child) {
131 0           return;
132             }
133              
134 0           delete $_[HEAP]{children_by_pid}{$child->PID};
135 0           };
136              
137             #This is where we're claiming the child is gone
138             my $on_child_signal = sub {
139 0     0     my $child = delete $_[HEAP]{children_by_pid}{$_[ARG1]};
140 0           $message->{runner_message_type} = 'finish';
141 0           $message->{runner_exit_code} = $_[ARG2];
142 0           $message->{runner_run_time} = time - $message->{runner_start_time};
143              
144 0 0         if($return_type eq 'gather') {
    0          
145             #noop, because we've already gathered the STDOUT/ERR
146             } elsif($return_type eq 'json') {
147             #noop, because we've already sent any and all messages
148             }
149 0           $self->emit($message);
150             # May have been reaped by on_child_close().
151 0 0         return unless defined $child;
152              
153 0           delete $_[HEAP]{children_by_wid}{$child->ID};
154 0           };
155 0           POE::Session->create(
156             inline_states => {
157             _start => $on_start,
158             got_child_stdout => $on_child_stdout,
159             got_child_stderr => $on_child_stderr,
160             got_child_close => $on_child_close,
161             got_child_signal => $on_child_signal,
162             }
163             );
164             }
165             }
166              
167             =head2 set_config
168              
169             =cut
170             sub set_config {
171 0     0 1   my $self = shift;
172 0           my $config = shift;
173 0           $self->{config} = $config;
174 0           my $state = $self->{state};
175             }
176              
177              
178             =head1 AUTHOR
179              
180             Dana M. Diederich, C<< <dana@realms.org> >>
181              
182             =head1 BUGS
183              
184             Please report any bugs or feature requests through L<https://github.com/dana/perl-App-MultiModule-Tasks-Runner/issues>. I will be notified, and then you'll
185             automatically be notified of progress on your bug as I make changes.
186              
187             =head1 SUPPORT
188              
189             You can find documentation for this module with the perldoc command.
190              
191             perldoc App::MultiModule::Tasks::Runner
192              
193              
194             You can also look for information at:
195              
196             =over 4
197              
198             =item * Report bugs here:
199              
200             L<https://github.com/dana/perl-App-MultiModule-Tasks-Runner/issues>
201              
202             =item * AnnoCPAN: Annotated CPAN documentation
203              
204             L<http://annocpan.org/dist/App-MultiModule-Tasks-Runner>
205              
206             =item * CPAN Ratings
207              
208             L<http://cpanratings.perl.org/d/App-MultiModule-Tasks-Runner>
209              
210             =item * Search CPAN
211              
212             L<https://metacpan.org/module/App::MultiModule::Tasks::Runner>
213              
214             =back
215              
216             =head1 ACKNOWLEDGEMENTS
217              
218             =head1 LICENSE AND COPYRIGHT
219              
220             Copyright 2016 Dana M. Diederich.
221              
222             This program is free software; you can redistribute it and/or modify it
223             under the terms of the the Artistic License (2.0). You may obtain a
224             copy of the full license at:
225              
226             L<http://www.perlfoundation.org/artistic_license_2_0>
227              
228             Any use, modification, and distribution of the Standard or Modified
229             Versions is governed by this Artistic License. By using, modifying or
230             distributing the Package, you accept this license. Do not use, modify,
231             or distribute the Package, if you do not accept this license.
232              
233             If your Modified Version has been derived from a Modified Version made
234             by someone other than you, you are nevertheless required to ensure that
235             your Modified Version complies with the requirements of this license.
236              
237             This license does not grant you the right to use any trademark, service
238             mark, tradename, or logo of the Copyright Holder.
239              
240             This license includes the non-exclusive, worldwide, free-of-charge
241             patent license to make, have made, use, offer to sell, sell, import and
242             otherwise transfer the Package with respect to any patent claims
243             licensable by the Copyright Holder that are necessarily infringed by the
244             Package. If you institute patent litigation (including a cross-claim or
245             counterclaim) against any party alleging that the Package constitutes
246             direct or contributory patent infringement, then this Artistic License
247             to you shall terminate on the date that such litigation is filed.
248              
249             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
250             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
251             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
252             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
253             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
254             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
255             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
256             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
257              
258              
259             =cut
260              
261             1; # End of App::MultiModule::Tasks::Runner