File Coverage

blib/lib/Reflex/Role/PidCatcher.pm
Criterion Covered Total %
statement 39 39 100.0
branch 9 16 56.2
condition n/a
subroutine 10 10 100.0
pod 0 3 0.0
total 58 68 85.2


line stmt bran cond sub pod time code
1             package Reflex::Role::PidCatcher;
2             # vim: ts=2 sw=2 noexpandtab
3             $Reflex::Role::PidCatcher::VERSION = '0.100';
4 1     1   1070 use Reflex::Role;
  1         2  
  1         4  
5              
6 1     1   871 use Scalar::Util qw(weaken);
  1         2  
  1         81  
7 1     1   604 use Reflex::Event::SigChild;
  1         3  
  1         599  
8              
9             attribute_parameter att_active => "active";
10             attribute_parameter att_pid => "pid";
11             callback_parameter cb_exit => qw( on att_pid exit );
12             method_parameter method_pause => qw( pause att_pid _ );
13             method_parameter method_resume => qw( resume att_pid _ );
14             method_parameter method_start => qw( start att_pid _ );
15             method_parameter method_stop => qw( stop att_pid _ );
16              
17             # A session may only watch a distinct pid once.
18             # So we must map each distinct pid to all the interested objects.
19             # This is class scoped data.
20             #
21             # TODO - We could put this closer to the POE::Session and obviate the
22             # need for the deliver() redirector.
23              
24             my %callbacks;
25              
26             sub deliver {
27 2     2 0 10 my ($class, $signal_name, $pid, $exit, @etc) = @_;
28              
29             # If nobody's watching us, then why did we do it in the road?
30             # TODO - Diagnostic warning/error?
31 2 50       7 return unless exists $callbacks{$pid};
32              
33             # Deliver the signal.
34             # TODO - map() magic to speed this up?
35              
36 2         3 foreach my $callback_recs (values %{$callbacks{$pid}}) {
  2         8  
37 2         4 foreach my $callback_rec (values %$callback_recs) {
38 2         8 my ($object, $method) = @$callback_rec;
39              
40 2         112 $object->$method(
41             Reflex::Event::SigChild->new(
42             _emitters => [ $object ],
43             signal => $signal_name,
44             pid => $pid,
45             exit => $exit,
46             )
47             );
48             }
49             }
50             }
51              
52             # The role itself.
53              
54             role {
55             my $p = shift;
56              
57             my $att_active = $p->att_active();
58             my $att_pid = $p->att_pid();
59             my $cb_exit = $p->cb_exit();
60              
61             requires $att_active, $att_pid, $cb_exit;
62              
63             my $method_start = $p->method_start();
64             my $method_stop = $p->method_stop();
65             my $method_pause = $p->method_pause();
66             my $method_resume = $p->method_resume();
67              
68             # Work around a Moose edge case.
69       2 0   sub BUILD {}
70              
71             after BUILD => sub {
72             my $self = shift();
73             return unless $self->$att_active();
74             $self->$method_start();
75             return;
76             };
77              
78             # Work around a Moose edge case.
79       2 0   sub DEMOLISH {}
80              
81             after DEMOLISH => sub {
82             shift()->$method_stop();
83             };
84              
85             method $method_start => sub {
86 2     2   4 my $self = shift;
87              
88 2         43 my $pid_value = $self->$att_pid();
89              
90             # Register this object with that PID.
91 2         22 $callbacks{$pid_value}->{$self->session_id()}->{$self} = [
92             $self, $cb_exit
93             ];
94 2         10 weaken $callbacks{$pid_value}->{$self->session_id()}->{$self}->[0];
95              
96             # First time this object is watching that PID? Start the
97             # watcher. Otherwise, a watcher should already be going.
98              
99             return if (
100 2 50       4 (scalar keys %{$callbacks{$pid_value}->{$self->session_id()}}) > 1
  2         8  
101             );
102              
103 2         12 $self->$method_resume();
104             };
105              
106             method $method_pause => sub {
107 2     2   3 my $self = shift;
108              
109             # Be in the session associated with this object.
110 2 50       5 return unless $self->call_gate($method_pause);
111              
112 2         69 $POE::Kernel::poe_kernel->sig_child($self->$att_pid(), undef);
113             };
114              
115             method $method_resume => sub {
116 4     4   6 my $self = shift;
117              
118             # Be in the session associated with this object.
119 4 100       16 return unless $self->call_gate($method_resume);
120              
121 2         65 $POE::Kernel::poe_kernel->sig_child(
122             $self->$att_pid(), "signal_happened", ref($self)
123             );
124             };
125              
126             method $method_stop => sub {
127 2     2   4 my $self = shift;
128              
129 2         57 my $pid_value = $self->$att_pid();
130              
131             # Nothing to do?
132 2 50       9 return unless exists $callbacks{$pid_value}->{$self->session_id()};
133              
134             # Unregister this object with that signal.
135 2         6 my $sw = $callbacks{$pid_value}->{$self->session_id()};
136 2 50       7 return unless delete $sw->{$self};
137              
138             # Deactivate the signal watcher if this was the last object.
139 2 50       6 unless (scalar keys %$sw) {
140 2         6 delete $callbacks{$pid_value}->{$self->session_id()};
141             delete $callbacks{$pid_value} unless (
142 2 50       2 scalar keys %{$callbacks{$pid_value}}
  2         8  
143             );
144 2         10 $self->$method_pause();
145             }
146             };
147             };
148              
149             __END__
150              
151             =pod
152              
153             =encoding UTF-8
154              
155             =for :stopwords Rocco Caputo
156              
157             =head1 NAME
158              
159             Reflex::Role::PidCatcher - add async process reaping behavior to a class
160              
161             =head1 VERSION
162              
163             This document describes version 0.100, released on April 02, 2017.
164              
165             =head1 SYNOPSIS
166              
167             package Reflex::PID;
168              
169             use Moose;
170             extends 'Reflex::Base';
171              
172             has pid => (
173             is => 'ro',
174             isa => 'Int',
175             required => 1,
176             );
177              
178             has active => (
179             is => 'ro',
180             isa => 'Bool',
181             default => 1,
182             );
183              
184             with 'Reflex::Role::PidCatcher' => {
185             pid => 'pid',
186             active => 'active',
187             cb_exit => 'on_exit',
188             method_start => 'start',
189             method_stop => 'stop',
190             method_pause => 'pause',
191             method_resume => 'resume',
192             };
193              
194             1;
195              
196             =head1 DESCRIPTION
197              
198             Reflex::Role::PidCatcher is a Moose parameterized role that adds
199             asynchronous child process reaping behavior to Reflex based classes.
200             The SYNOPSIS is the entire implementation of Reflex::PID, a simple
201             class that allows Reflex::Role::PidCatcher to be used as an object.
202              
203             =head2 Required Role Parameters
204              
205             None. All role parameters as of this writing have what we hope are
206             sensible defaults. Please let us know if they don't seem all that
207             sensible.
208              
209             =head2 Optional Role Parameters
210              
211             =head3 pid
212              
213             C<pid> sets the name of an attribute that will contain the process ID
214             to wait for. Process IDs must be integers.
215              
216             =head3 active
217              
218             C<active> specifies whether Reflex::Role::PidCatcher should be created
219             in the active, process-watching state. All Reflex watchers are
220             enabled by default. Set it to a false value, preferably 0, to
221             initialize the catcher in an inactive or paused mode.
222              
223             Process watchers may currently be paused and resumed, but this
224             functionality may be dropped later. It's not good to leave child
225             processes hanging. See C<method_pause> and C<method_resume> for ways
226             to override the default method names.
227              
228             =head3 cb_exit
229              
230             C<cb_exit> names the $self method that will be called when the child
231             process identified in C<<$self->$pid()>> exits. It defaults to
232             "on_%s_exit", where %s is the name of the PID attribute. For example,
233             it will be "on_transcoder_exit" if the process ID is stored in a
234             "transcoder" attribute.
235              
236             =head3 method_start
237              
238             C<method_start> sets the name of the method that may be used to start
239             watching for a process exit. It's "start_%s" by default, where %s is
240             the name of the process ID's attribute.
241              
242             Reflex::Role::PidCatcher will automatically start watching for its
243             process ID if the value of C<active>'s attribute is true.
244              
245             =head3 method_stop
246              
247             C<method_stop> may be used to permanently stop a process ID watcher.
248             Stopped watchers cannot be restarted, so use C<method_pause> if you
249             need to temporarily disable them instead. C<method_resume> may be
250             used to resume them again.
251              
252             Process ID catchers will automatically stop watching for process exit
253             upon DEMOLISH.
254              
255             =head3 method_pause
256              
257             C<method_pause> sets the name of the method that may be used to pause
258             process catching. It is "pause_%s" by default, where %s is the name
259             of the PID attribute.
260              
261             =head3 method_resume
262              
263             C<method_resume> sets the name of the method that may be used to
264             resume process reaping. It is "resume_%s" by default, where %s is the
265             name of the attribute holding the process ID.
266              
267             =for Pod::Coverage BUILD DEMOLISH deliver
268              
269             =head1 EXAMPLES
270              
271             Reflex::Role::PidCatcher was initially written to support Reflex::PID,
272             so there aren't many examples of the role's use by itself.
273              
274             L<Reflex::POE::Wheel::Run> actualy uses Reflex::PID.
275              
276             eg/eg-07-wheel-run.pl uses Reflex::POE::Wheel::Run.
277              
278             =head1 SEE ALSO
279              
280             Please see those modules/websites for more information related to this module.
281              
282             =over 4
283              
284             =item *
285              
286             L<Reflex|Reflex>
287              
288             =item *
289              
290             L<Reflex>
291              
292             =item *
293              
294             L<Reflex::Role::SigCatcher>
295              
296             =item *
297              
298             L<Reflex::Signal>
299              
300             =item *
301              
302             L<Reflex::Role::PidCatcher>
303              
304             =item *
305              
306             L<Reflex::PID>
307              
308             =item *
309              
310             L<Reflex/ACKNOWLEDGEMENTS>
311              
312             =item *
313              
314             L<Reflex/ASSISTANCE>
315              
316             =item *
317              
318             L<Reflex/AUTHORS>
319              
320             =item *
321              
322             L<Reflex/BUGS>
323              
324             =item *
325              
326             L<Reflex/BUGS>
327              
328             =item *
329              
330             L<Reflex/CONTRIBUTORS>
331              
332             =item *
333              
334             L<Reflex/COPYRIGHT>
335              
336             =item *
337              
338             L<Reflex/LICENSE>
339              
340             =item *
341              
342             L<Reflex/TODO>
343              
344             =back
345              
346             =head1 BUGS AND LIMITATIONS
347              
348             You can make new bug reports, and view existing ones, through the
349             web interface at L<http://rt.cpan.org/Public/Dist/Display.html?Name=Reflex>.
350              
351             =head1 AUTHOR
352              
353             Rocco Caputo <rcaputo@cpan.org>
354              
355             =head1 COPYRIGHT AND LICENSE
356              
357             This software is copyright (c) 2017 by Rocco Caputo.
358              
359             This is free software; you can redistribute it and/or modify it under
360             the same terms as the Perl 5 programming language system itself.
361              
362             =head1 AVAILABILITY
363              
364             The latest version of this module is available from the Comprehensive Perl
365             Archive Network (CPAN). Visit L<http://www.perl.com/CPAN/> to find a CPAN
366             site near you, or see L<https://metacpan.org/module/Reflex/>.
367              
368             =head1 DISCLAIMER OF WARRANTY
369              
370             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
371             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT
372             WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER
373             PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND,
374             EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
375             IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
376             PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
377             SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME
378             THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
379              
380             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
381             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
382             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE
383             TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR
384             CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
385             SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
386             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
387             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
388             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
389             DAMAGES.
390              
391             =cut