File Coverage

blib/lib/App/MultiModule/API.pm
Criterion Covered Total %
statement 15 213 7.0
branch 0 138 0.0
condition 0 6 0.0
subroutine 5 21 23.8
pod 12 12 100.0
total 32 390 8.2


line stmt bran cond sub pod time code
1             package App::MultiModule::API;
2             $App::MultiModule::API::VERSION = '1.143160';
3 35     35   1495997 use strict;use warnings;
  35     35   57  
  35         1175  
  35         140  
  35         43  
  35         989  
4 35     35   694 use Data::Dumper;
  35         6906  
  35         1962  
5 35     35   158 use Sereal::Encoder qw(encode_sereal);
  35         41  
  35         1979  
6 35     35   145 use Sereal::Decoder qw(looks_like_sereal decode_sereal);
  35         35  
  35         66534  
7              
8              
9             =head2 new
10              
11             Constructor
12              
13             =over 4
14              
15             =item state
16              
17             Directory path where various run-time files are kept. Defaults to state/.
18              
19             =back
20              
21              
22             =cut
23             sub new {
24 0     0 1   my $class = shift;
25 0           my %args = @_;
26 0           my $debug = $args{debug};
27 0 0         $debug = 5 unless defined $debug;
28 0 0         $args{state_dir} = 'state' unless $args{state_dir};
29 0           my $self = {
30             state_dir => $args{state_dir},
31             };
32 0           bless ($self, $class);
33              
34 0           return $self;
35             }
36              
37             =head1 METHODS
38              
39             =cut
40             sub _read_file {
41 0     0     my $self = shift;
42 0           my $filename = shift;
43 0           my %args = @_;
44 0 0         return undef unless my $state_dir = $self->{state_dir};
45 0 0         mkdir $state_dir unless -e $state_dir;
46 0 0         return undef unless -r "$state_dir/$filename";
47 0           my $ret;
48 0           eval {
49 0           my $f;
50 0 0         die "passed state_dir $state_dir not writable"
51             unless -w $state_dir;
52 0 0         die "passed state_dir $state_dir not a directory"
53             unless -d $state_dir;
54 0           my $file_path = "$state_dir/$filename";
55 0 0         open my $fh, '<', $file_path or die "failed to open $file_path for reading: $!";
56 0           while(<$fh>) {
57 0           $f .= $_;
58             }
59 0 0         close $fh or die "failed to close $file_path: $!";
60 0 0         if(looks_like_sereal($f)) {
61 0 0         $ret = decode_sereal $f or die 'returned false';
62             } else {
63 0 0         $ret = do $file_path or die "failed to deserialize $file_path: $@";
64             }
65             };
66 0 0         die "App::MultiModule::API::_read_file failed: $@\n" if $@;
67 0           return $ret;
68             }
69              
70              
71             sub _write_file {
72 0     0     my $self = shift;
73 0           my $filename = shift;
74 0           my $contents = shift;
75 0           my %args = @_;
76 0           eval {
77 0 0         mkdir $self->{state_dir} unless -e $self->{state_dir};
78 0 0         open my $fh, '>', "$self->{state_dir}/$filename.tmp"
79             or die "open failed: $!\n";
80             # print $fh Data::Dumper::Dumper $contents or die "print failed: $!\n";
81 0 0         print $fh encode_sereal $contents or die "print failed: $!\n";
82 0 0         close $fh or die "close failed: $!\n";
83 0 0         rename "$self->{state_dir}/$filename.tmp","$self->{state_dir}/$filename" or die "rename failed: $!\n";
84             };
85 0 0         die "App::MultiModule::API::_write_file failed: $@\n" if $@;
86             }
87              
88             sub _my_read_file {
89 0     0     my $path = shift;
90 0 0         open my $fh, '<', $path or die "failed to open $path: $!\n";
91 0 0         read $fh, my $ret, 1024000
92             or die "failed to read from $path: $!\n";
93 0 0         close $fh or die "failed to close $path: $!\n";
94 0           return $ret;
95             }
96              
97             =head2 get_task_status($task_name)
98              
99             Returns the saved status
100              
101             Example:
102             status => {
103             'is_my_pid' => 0,
104             'is_running' => 1,
105             'save_ts' => 1370987265,
106             'task_count' => 1,
107             'fd_count' => 5,
108             'cmdline' => '/usr/bin/perlbin/MultiModule-qtqueue-pMultiModuleTest::-oalert:test_alert_queue,this:that-mOtherExternalModule',
109             'stat' => '3577 (MultiModule) S 1 13564 19489 34848 13564 4202496 3202 0 0 0 16 3 0 0 20 0 1 0 36033429 71139328 2949 18446744073709551615 4194304 4198756 140735555873936 140735555873176 139685157236755 0 0 4096 16384 18446744071580469929 0 0 17 2 0 0 0 0 0
110             ',
111             'pid' => 3577,
112             'statm' => '17368 2949 624 2 0 5133 0
113             },
114              
115             =cut
116             sub get_task_status {
117 0     0 1   my $self = shift;
118 0           my $task_name = shift;
119 0           my %args = @_;
120              
121 0           my $status;
122 0           eval {
123 0 0         $status = $self->_read_file($task_name . '.status')
124             or die 'no status';
125             };
126 0 0         if($@) {
127             return {
128 0           initial_state => 1,
129             };
130             }
131              
132 0           my $status_pid; my $status_cmdline;
133 0           eval {
134 0           $status_pid = $status->{pid};
135 0 0         die "loaded status does not have required attribute 'cmdline'"
136             unless $status_cmdline = $status->{cmdline};
137 0 0         die "loaded status does not have required attribute 'save_ts'"
138             unless my $status_save_ts = $status->{save_ts};
139             };
140 0 0         if($@) {
141 0 0         die "App::MultiModule::API::get_task_status: $@\n" if $@;
142             }
143              
144             #default the status to 'not running'
145 0           $status->{is_running} = 0;
146 0           $status->{is_my_pid} = 0;
147             #what does it take to claim the process in the status file is
148             #running
149             #1. the PID has to be running
150             #2. the cmdline saved in the status file has to match the cmdline running
151 0 0         if($status_pid) {
152 0           eval {
153 0           my $status_pid_cmdline = _my_read_file("/proc/$status_pid/cmdline");
154 0           chomp $status_cmdline; chomp $status_pid_cmdline;
  0            
155 0 0         die "not running\n" if $status_cmdline ne $status_pid_cmdline;
156              
157 0           $status->{is_running} = 1;
158 0 0         $status->{is_my_pid} = 1
159             if $status_pid == $$;
160             $status->{$_} = _my_read_file("/proc/$status_pid/$_")
161 0           for ('stat','statm');
162 0 0         if(-d "/proc/$status_pid/fd/") {
163 0           eval {
164 0 0         opendir my $dh, "/proc/$status_pid/fd/" or die;
165 0 0         my @files = grep { not /^\./ } readdir $dh or die;
  0            
166 0           closedir $dh;
167 0           $status->{fd_count} = scalar @files;
168             };
169             }
170 0 0         if(-d "/proc/$status_pid/task/") {
171 0           eval {
172 0 0         opendir my $dh, "/proc/$status_pid/task/" or die;
173 0 0         my @files = grep { not /^\./ } readdir $dh or die;
  0            
174 0           closedir $dh;
175 0           $status->{task_count} = scalar @files;
176             };
177             }
178             };
179 0 0         if($@) {
180             #all of these are ignored, because it just means the process isn't
181             #running
182             }
183             }
184              
185 0           return $status;
186             }
187              
188             =head2 get_task_config($task_name)
189              
190             Return the tasks's config.
191              
192             Example:
193             config => {
194             'increment_by' => 8427,
195             'is_external' => 1
196             };
197             =cut
198             {
199             my $time_slice;
200             my $cache;
201             sub get_task_config {
202 0     0 1   my $self = shift;
203 0           my $task_name = shift;
204 0           my %args = @_;
205 0 0         if(not $time_slice) {
206 0           $time_slice = {};
207 0           $cache = {};
208             }
209 0 0         $time_slice->{$task_name} = time unless $time_slice->{$task_name};
210 0 0 0       if($cache->{$task_name} and $time_slice->{$task_name} == time) {
211             # print STDERR "returning cached config for $task_name\n";
212 0           return $cache->{$task_name};
213             }
214 0           $time_slice->{$task_name} = time;
215              
216 0           my $config;
217 0           eval {
218 0           $config = $self->_read_file($task_name . '.conf');
219             };
220             # $self->debug("get_task_config for $task_name failed: $@") if $@;
221 0 0         print STDERR "get_task_config for $task_name failed: $@\n" if $@;
222 0           $cache->{$task_name} = $config;
223 0           return $config;
224             }
225             }
226              
227             =head2 get_task_state($task_name)
228              
229             Returns the saved state
230              
231             Example:
232             state =>
233             '.multimodule' => {
234             'save_ts' => 1370987685
235             },
236             'most_recent' => 10246,
237             'sum_increment_by' => 6585
238              
239             }
240              
241             =cut
242             sub get_task_state {
243 0     0 1   my $self = shift;
244 0           my $task_name = shift;
245 0           my %args = @_;
246              
247 0           my $state;
248 0           eval {
249 0 0         $state = $self->_read_file($task_name . '.state')
250             or die 'no state';
251             };
252 0 0         if($@) {
253             return {
254 0           '.multimodule' => {
255             initial_state => 1,
256             },
257             };
258             }
259             # die "App::MultiModule::API::get_task_state failed: $@\n" if $@;
260              
261 0           return $state;
262             }
263              
264              
265             =head2 unfailsafe_task($task_name)
266              
267             Causes a task to no longer be failsafe
268              
269             =cut
270             sub unfailsafe_task {
271 0     0 1   my $self = shift;
272 0           my $task_name = shift;
273 0           my %args = @_;
274 0 0         return undef unless my $state_dir = $self->{state_dir};
275 0           my $file = "$state_dir/$task_name.failsafe";
276 0 0         return 1 unless -e $file;
277 0 0         unlink $file
278             or die "App::MultiModule::API::unfailsafe_task: unable to unlink $file: $!";
279 0           return 1;
280             }
281              
282             =head2 failsafe_task($task_name)
283              
284             Marks a task as failsafed.
285              
286             =cut
287             sub failsafe_task {
288 0     0 1   my $self = shift;
289 0           my $task_name = shift;
290 0           my $message = shift;
291 0           my %args = @_;
292 0 0         return undef unless my $state_dir = $self->{state_dir};
293 0     0     local $SIG{ALRM} = sub { die "timed out\n"; };
  0            
294 0           my $file = "$state_dir/$task_name.failsafe";
295 0           alarm 2;
296 0           eval {
297 0 0         open my $fh, '>', $file
298             or die "failed to open $file for writing: $!";
299 0 0         print $fh Data::Dumper::Dumper $message
300             or die "failed to write to $file: $!";
301 0 0         close $fh
302             or die "failed to $file: $!";
303             };
304 0           alarm 0;
305 0 0         die "App::MultiModule::API::failsafe_task: failed: $@" if $@;
306 0           return 1;
307             }
308              
309             =head2 task_is_failsafe($task_name)
310              
311             Returns 'true' if the task is failsafe
312              
313             =cut
314             sub task_is_failsafe {
315 0     0 1   my $self = shift;
316 0           my $task_name = shift;
317 0           my %args = @_;
318 0 0         return undef unless my $state_dir = $self->{state_dir};
319 0 0         return 1 if -e "$state_dir/$task_name.failsafe";
320             }
321              
322              
323             =head2 save_task_status($task_name, $status)
324              
325             Save the task status.
326              
327             =cut
328             sub save_task_status {
329 0     0 1   my $self = shift;
330 0           my $task_name = shift; my $status = shift;
  0            
331 0           my %args = @_;
332 0 0         $status = {} unless $status;
333 0 0         if($args{no_save_pid}) {
334 0           delete $status->{pid};
335             } else {
336 0           $status->{pid} = $$;
337             }
338 0           $status->{save_ts} = time;
339 0           { open my $fh, '<', "/proc/$$/cmdline";
  0            
340 0           read $fh, $status->{cmdline}, 10240;
341 0           close $fh;
342             }
343 0           eval {
344 0           $self->_write_file($task_name . '.status', $status);
345             };
346 0 0         print STDERR "save_task_status _write_file call failed: $@\n" if $@;
347             }
348              
349             =head2 save_task_state($task_name, $state)
350              
351             Save the task state.
352              
353             =cut
354             sub save_task_state {
355 0     0 1   my $self = shift;
356 0           my $task_name = shift;my $state = shift;
  0            
357 0           my %args = @_;
358 0 0         $state->{'.multimodule'} = {} unless $state->{'.multimodule'};
359 0           my $m = $state->{'.multimodule'};
360             # if($args{no_save_pid}) {
361             # delete $m->{pid};
362             # } else {
363             # $m->{pid} = $$;
364             # }
365 0           $m->{save_ts} = time;
366             # { open my $fh, '<', "/proc/$$/cmdline";
367             # read $fh, $m->{cmdline}, 10240;
368             # close $fh;
369             # }
370 0           eval {
371 0           $self->_write_file($task_name . '.state', $state);
372             };
373             }
374              
375             =head2 get_task_status_files
376              
377             Return an array of state files.
378              
379             =cut
380             sub get_task_status_files {
381 0     0 1   my $self = shift;
382 0           my %args = @_;
383 0 0         return undef unless my $state_dir = $self->{state_dir};
384              
385 0           my @files = ();
386 0           eval {
387 0 0         opendir(my $dh, $state_dir) or die "failed to opendir $state_dir: $!";
388 0 0 0       foreach my $file (
  0            
389             grep { not /^\./ and -f "$state_dir/$_" and /\.status$/ }
390             readdir($dh)) {
391 0           $file =~ s/\..*//;
392 0           push @files, $file;
393             }
394 0 0         closedir $dh or die "failed to closedir $state_dir: $!\n";
395             };
396 0           return @files;
397             }
398              
399             =head2 save_task_config($task_name, $config)
400              
401             Save the task config.
402              
403             =cut
404             sub save_task_config {
405 0     0 1   my $self = shift;
406 0           my $task_name = shift;my $config = shift;
  0            
407 0           my %args = @_;
408 0           eval {
409 0           $self->_write_file($task_name . '.conf', $config);
410             };
411             #$self->debug("save_task_config for $task_name failed: $@") if $@;
412 0 0         print STDERR "save_task_config for $task_name failed: $@\n" if $@;
413             }
414              
415             =head2 send_signal($task_name, $integer_UNIX_signal)
416              
417             Send specified signal to task.
418              
419             =cut
420             sub send_signal {
421 0     0 1   my $self = shift;
422 0           my $task_name = shift; my $signal = shift;
  0            
423 0           my %args = @_;
424 0           my $pid;
425 0           eval {
426 0 0         die "undef\n" unless my $status = $self->get_task_status($task_name);
427 0 0         die "undef\n" unless $pid = $status->{pid};
428 0 0         die "undef\n" if $pid == $$;
429             };
430 0 0         print STDERR "$$: send_signal failed: $@\n" if $@;
431 0 0         return undef if $@;
432 0 0         return undef unless $pid;
433 0           return kill $signal, $pid;
434             }
435              
436              
437              
438             1;