File Coverage

blib/lib/POE/Component/ProcTerminator.pm
Criterion Covered Total %
statement 92 122 75.4
branch 6 22 27.2
condition 4 13 30.7
subroutine 23 24 95.8
pod 1 1 100.0
total 126 182 69.2


line stmt bran cond sub pod time code
1             package POE::Component::ProcTerminator::Batch;
2 2     2   224945 use strict;
  2         6  
  2         70  
3 2     2   12 use warnings;
  2         5  
  2         63  
4 2     2   1841 use Proc::Terminator;
  2         188471  
  2         151  
5 2     2   19 use Moo;
  2         4  
  2         9  
6              
7             extends 'Proc::Terminator::Batch';
8             has timer_id => (is => 'rw');
9             has failure_callback => (
10             is => 'ro',
11             isa => sub { ref $_[0] eq 'CODE'
12             or die "Failure callback should be a CODE reference" }
13             );
14             has cleanup_flags => ( is => 'rw', default => 0 );
15              
16             package POE::Component::ProcTerminator;
17 2     2   720 use warnings;
  2         4  
  2         46  
18 2     2   10 use strict;
  2         2  
  2         62  
19 2     2   2017 use POE::Sugar::Attributes;
  2         253089  
  2         92  
20 2     2   20 use POE;
  2         3  
  2         16  
21 2     2   600 use POE::Session;
  2         12  
  2         9  
22 2     2   102 use Proc::Terminator;
  2         3  
  2         96  
23 2     2   17 use base qw(POE::Sugar::Attributes Exporter);
  2         3  
  2         198  
24 2     2   11 use Class::Struct;
  2         4  
  2         11  
25 2     2   220 use Time::HiRes qw(time);
  2         9  
  2         18  
26 2     2   163 use POSIX qw(:signal_h);
  2         4  
  2         22  
27              
28             our @EXPORT;
29             our $DefaultSession = "proc_terminator";
30              
31 2         22 use Constant::Generate [qw(
32             SIGKILL_LAST
33             SIGKILL_ONLY
34             CLEANUP_BLOCK
35 2     2   737 )], -type => 'bit', -export => 1, -prefix => 'PROCTERMf_';
  2         5  
36              
37             struct
38             'POE::Component::ProcTerminator::SessionInfo' =>
39             [
40             # hash of 'batches', indexed by their timer IDs
41             batches => '*%',
42            
43             # default options
44             defaults => '*%'
45            
46             ];
47            
48             our $VERSION = 0.03;
49              
50              
51             sub spawn {
52 1     1 1 1799 my ($cls,%options) = @_;
53 1         4 my $alias = delete $options{Alias};
54 1   33     5 $alias ||= $DefaultSession;
55              
56 1         6 my $inline_states =
57             POE::Sugar::Attributes->inline_states(__PACKAGE__, $alias);
58            
59 1         1358 my $sessinfo = POE::Component::ProcTerminator::SessionInfo->new();
60 1         73 $sessinfo->defaults(\%options);
61 1         20 POE::Session->create(
62             inline_states => $inline_states,
63             heap => $sessinfo
64             );
65             }
66              
67             sub _do_terminate :Event(terminate)
68             {
69 11     11   56930 my ($sessinfo,$pids,$options) = @_[HEAP, ARG0..ARG2];
70 11   50     58 $options ||= {};
71 11         22 $options = { %{ $sessinfo->defaults }, %$options };
  11         1742  
72            
73 11   50     759 $options->{cleanup_flags} ||= 0;
74            
75 11 50       42 if (delete $options->{max_wait}) {
76 0         0 warn "max_wait does not make sense in POE::Component::ProcTerminator";
77             }
78 11         414 my $batch = POE::Component::ProcTerminator::Batch->with_pids(
79             $pids,
80             max_wait => 0,
81             %$options
82             );
83 11         13487 $batch->begin_time(time);
84 11         52 my $ret = $batch->loop_once;
85 11 50 0     3549 if ($ret) {
    0          
86 11         237 my $new_aid =$_[KERNEL]->delay_set(
87             _grace_next => $batch->grace_period,
88             $batch
89             );
90 11         2818 $batch->timer_id($new_aid);
91             } elsif ((!defined $ret) && $batch->failure_callback) {
92 0         0 $batch->failure_callback->($batch);
93             }
94 2     2   1097 }
  2         4  
  2         10  
95              
96             sub _grace_next :Event {
97 13     13   1430966 my ($sessinfo,$batch) = @_[HEAP,ARG0];
98 13         139 my $ret = $batch->loop_once();
99 13 100       5080 if ($ret) {
100 2         34 my $new_aid = $_[KERNEL]->delay_set(
101             $_[STATE],
102             $batch->grace_period,
103             $batch,
104             );
105 2         236 $batch->timer_id($new_aid);
106            
107             } else {
108 11 50 33     68 if ( (!defined $ret) && $batch->failure_callback ) {
109 0         0 $batch->failure_callback->($batch);
110             }
111 11         443 delete $sessinfo->batches->{$batch->timer_id};
112             }
113 2     2   744 }
  2         3  
  2         7  
114              
115              
116             sub _unknown :Event(_default)
117             {
118 0     0   0 my ($event,$args) = @_[ARG0,ARG1];
119 0         0 warn "Unhandled Event '$event'";
120 2     2   447 }
  2         4  
  2         8  
121              
122              
123             sub _clean_all :Stop {
124 1     1   863 my $sessinfo = $_[HEAP];
125            
126             # iterate through all the batches. Figure out which ones should
127             # be killed immediately
128 1         22 my $remaining_batch = Proc::Terminator::Batch->with_pids([]);
129 1         3220 my $counter = 0;
130            
131 1         3 while (my ($aid,$batch) = each %{$sessinfo->batches}) {
  1         23  
132 0 0       0 if ($batch->cleanup_flags & PROCTERMf_SIGKILL_ONLY) {
    0          
133 0         0 $batch->cleanup_flags(0);
134 0         0 kill (SIGKILL, $batch->pid);
135 0         0 $_[KERNEL]->alarm_remove($batch->timer_id);
136             } elsif ($batch->cleanup_flags & PROCTERMf_SIGKILL_LAST) {
137 0         0 while (my ($pid,$ctx) = each %{$batch->procs}) {
  0         0  
138 0 0       0 if ($ctx->siglist->[$#{$ctx->siglist}] != SIGKILL) {
  0         0  
139 0         0 push @{$ctx->siglist}, SIGKILL;
  0         0  
140             }
141             }
142             }
143            
144 0         0 delete $sessinfo->batches->{$aid};
145            
146 0 0       0 if (($batch->cleanup_flags & PROCTERMf_CLEANUP_BLOCK) == 0) {
147 0         0 $batch->loop_once();
148             } else {
149             # cleanup..
150 0         0 @{$remaining_batch->procs}{keys %{$batch->procs}} =
  0         0  
  0         0  
151 0         0 values %{$batch->procs};
152             }
153             }
154            
155 1 50       11 if (%{$remaining_batch->procs}) {
  1         26  
156 0         0 $remaining_batch->max_wait(5); # configurable maybe?
157 0         0 $remaining_batch->begin_time(time);
158 0         0 while ($remaining_batch->loop_once) {
159 0         0 sleep(0.25);
160             }
161             }
162            
163             # ensure the final signal is indeed sent..
164 1         11 while (my ($pid,$ctx) = each %{$remaining_batch->procs}) {
  1         24  
165 0         0 my $sig = pop @{$ctx->siglist};
  0         0  
166 0 0       0 if ($sig) {
167 0         0 kill ($sig, $ctx->pid);
168             }
169             }
170 2     2   1040 }
  2         3  
  2         7  
171              
172             1;
173              
174             __END__