File Coverage

blib/lib/Proc/Launcher/Manager.pm
Criterion Covered Total %
statement 111 112 99.1
branch 13 16 81.2
condition 1 3 33.3
subroutine 21 21 100.0
pod 12 12 100.0
total 158 164 96.3


line stmt bran cond sub pod time code
1             package Proc::Launcher::Manager;
2 3     3   1758 use strict;
  3         6  
  3         82  
3 3     3   15 use warnings;
  3         4  
  3         115  
4              
5             our $VERSION = '0.0.35'; # VERSION
6              
7 3     3   2615 use Mouse;
  3         105136  
  3         17  
8              
9 3     3   1017 use Carp;
  3         6  
  3         274  
10 3     3   17 use File::Path;
  3         5  
  3         186  
11 3     3   2856 use POSIX qw(:sys_wait_h);
  3         23193  
  3         20  
12              
13 3     3   6228 use Proc::Launcher;
  3         10  
  3         112  
14 3     3   1894 use Proc::Launcher::Supervisor;
  3         6  
  3         4031  
15              
16             =head1 NAME
17              
18             Proc::Launcher::Manager - manage multiple Proc::Launcher objects
19              
20             =head1 VERSION
21              
22             version 0.0.35
23              
24             =head1 SYNOPSIS
25              
26             use Proc::Launcher::Manager;
27              
28             my $shared_config = { x => 1, y => 2 };
29              
30             my $monitor = Proc::Launcher::Manager->new( app_name => 'MyApp' );
31              
32             # a couple of different components
33             $monitor->register( daemon_name => 'component1',
34             start_method => sub { MyApp->start_component1( $config ) }
35             );
36             $monitor->register( daemon_name => 'component2',
37             start_method => sub { MyApp->start_component2( $config ) }
38             );
39              
40             # using class/method/context rather than a code ref
41             $monitor->register( daemon_name => 'webui',
42             class => 'MyApp::WebUI',
43             start_method => 'start_webui',
44             context => $config,
45             );
46              
47             # start all registered daemons. processes that are already
48             # running won't be restarted.
49             $monitor->start();
50              
51             # stop all daemons
52             $monitor->stop();
53             sleep 1;
54             $monitor->force_stop();
55              
56             # display all processes stdout/stderr from the log file that's
57             # been generated since we started
58             $monitor->read_log( sub { print "$_[0]\n" } );
59              
60             # get a specific daemon or perform actions on one
61             my $webui = $monitor->daemon('webui');
62             $monitor->daemon('webui')->start();
63              
64             # start the process supervisor. this will start up an event loop
65             # and won't exit until it is killed. any processes not already
66             # running will be started. all processes will be monitored and
67             # automatically restarted if they exit.
68             $monitor->supervisor->start();
69              
70             # shut down/restart the supervisor
71             $monitor->supervisor->stop();
72             $monitor->supervisor->force_stop();
73             $monitor->supervisor->restart();
74              
75              
76             =head1 DESCRIPTION
77              
78             This library makes it easier to deal with multiple L
79             processes by providing methods to start and stop all daemons with a
80             single command. Please see the documentation in L to
81             understand how this these libraries differ from other similar forking
82             and controller modules.
83              
84             It also provides a supervisor() method which will fork a daemon that
85             will monitor the other daemons at regular intervals and restart any
86             that have stopped. Note that only one supervisor can be running at
87             any given time for each pid_dir.
88              
89             There is no tracking of inter-service dependencies nor predictable
90             ordering of service startup. Instead, daemons should be designed to
91             wait for needed resources. See L if you need to
92             handle dependencies.
93              
94             =cut
95              
96             #_* Roles
97              
98             with 'Proc::Launcher::Roles::Launchable';
99              
100             #_* Attributes
101              
102             has 'debug' => ( is => 'ro',
103             isa => 'Bool',
104             default => 0,
105             );
106              
107             has 'pid_dir' => ( is => 'ro',
108             isa => 'Str',
109             lazy => 1,
110             default => sub {
111             my $dir = join "/", $ENV{HOME}, "logs";
112             unless ( -d $dir ) { mkpath( $dir ); }
113             return $dir;
114             },
115             );
116              
117             has 'launchers' => ( is => 'rw',
118             isa => 'HashRef[Proc::Launcher]',
119             );
120              
121              
122             has 'supervisor' => ( is => 'rw',
123             lazy => 1,
124             default => sub {
125             my $self = shift;
126              
127             return Proc::Launcher->new(
128             daemon_name => 'supervisor',
129             pid_dir => $self->pid_dir,
130             start_method => sub {
131             Proc::Launcher::Supervisor->new( manager => $self )->monitor();
132             },
133             );
134             },
135             );
136              
137              
138             #_* Methods
139              
140             =head1 METHODS
141              
142             =over 8
143              
144             =item register( %options )
145              
146             Create a new L object with the specified options. If
147             the specified daemon already exists, no changes will be made.
148              
149             =cut
150              
151             sub register {
152 4     4 1 4341 my ( $self, %options ) = @_;
153              
154 4         26 $options{pid_dir} = $self->pid_dir;
155              
156 4         11 my $daemon;
157 4 50       33 unless ( $self->{daemons}->{ $options{daemon_name} } ) {
158 4         109 $daemon = Proc::Launcher->new( %options );
159             }
160              
161             # just capturing the position of the tail end of the log file
162 4         34 $self->read_log( undef, $daemon );
163              
164 4         389 $self->{daemons}->{ $options{daemon_name} } = $daemon;
165             }
166              
167             =item daemon( 'daemon_name' )
168              
169             Return the L object for a specified daemon.
170              
171             =cut
172              
173             sub daemon {
174 25     25 1 53 my ( $self, $daemon_name ) = @_;
175              
176 25         215 return $self->{daemons}->{ $daemon_name };
177             }
178              
179              
180             =item daemons()
181              
182             Return a list of L objects.
183              
184             =cut
185              
186             sub daemons {
187 12     12 1 29 my ( $self ) = @_;
188              
189 12         27 my @daemons;
190              
191 12         144 for my $daemon_name ( $self->daemons_names() ) {
192 25         88 push @daemons, $self->{daemons}->{ $daemon_name };
193             }
194              
195 12         87 return @daemons;
196             }
197              
198             =item daemons_names()
199              
200             Return a list of the names of all registered daemons.
201              
202             =cut
203              
204             sub daemons_names {
205 19     19 1 1093 my ( $self ) = @_;
206              
207 19         39 return ( sort keys %{ $self->{daemons} } );
  19         408  
208             }
209              
210             =item is_running()
211              
212             Return a list of the names of daemons that are currently running.
213              
214             This will begin by calling rm_zombies() on one of daemon objects,
215             sleeping a second, and then calling rm_zombies() again. So far the
216             test cases have always passed when using this strategy, and
217             inconsistently passed with any subset thereof.
218              
219             While it seems that this is more complicated than shutting down a
220             single process, it's really just trying to be a bit more efficient.
221             When managing a single process, is_running might be called a few times
222             until the process exits. Since we might be managing a lot of daemons,
223             this method is likely to be a bit more efficient and will hopefully
224             only need to be called once (after the daemons have been given
225             necessary time to shut down).
226              
227             This may be reworked a bit in the future since calling sleep will halt
228             all processes in single-threaded cooperative multitasking frameworks.
229              
230             =cut
231              
232             sub is_running {
233 6     6 1 858 my ( $self ) = @_;
234              
235 6         59 my @daemon_names = $self->daemons_names();
236              
237 6 100       47 unless ( scalar @daemon_names ) {
238 1         439 print "is_running() called when no daemons registered\n";
239 1         10 return;
240             }
241              
242             # clean up deceased child processes before checking if processes
243             # are running.
244 5         70 $self->daemon($daemon_names[0])->rm_zombies();
245              
246             # # give any processes that have ceased a second to shut down
247 5         5000857 sleep 1;
248              
249             # again clean up deceased child processes before checking if
250             # processes are running.
251 5         72 $self->daemon($daemon_names[0])->rm_zombies();
252              
253 5         21 my @running = ();
254              
255 5         59 for my $daemon_name ( @daemon_names ) {
256 15 100       49 if ( $self->daemon($daemon_name)->is_running() ) {
257 6         23 push @running, $daemon_name
258             }
259             }
260              
261 5         145 return @running;
262             }
263              
264             =item start( $data )
265              
266             Call the start() method on all registered daemons. If the daemon is
267             already running it will not be restarted.
268              
269             =cut
270              
271             sub start {
272 4     4 1 372 my ( $self, $data ) = @_;
273              
274 4         11 my $started;
275              
276 4         183 for my $daemon ( $self->daemons() ) {
277 10 50       61 if ( $daemon->is_running() ) {
278 0         0 print "daemon already running: ", $daemon->daemon_name, "\n";
279             }
280             else {
281 10         2338 print "starting daemon: ", $daemon->daemon_name, "\n";
282 10         79 $daemon->start();
283 10         175 $started++;
284             }
285             }
286              
287 4         316 return $started;
288             }
289              
290              
291             =item stop()
292              
293             Call the stop() method on all daemons.
294              
295             =cut
296              
297             sub stop {
298 2     2 1 8 my ( $self ) = @_;
299              
300 2         49 for my $daemon ( $self->daemons() ) {
301 4         8467 print "stopping daemon: ", $daemon->daemon_name, "\n";
302 4         46 $daemon->stop();
303             }
304              
305 2         70 return 1;
306             }
307              
308             =item force_stop()
309              
310             Call the force_stop method on all daemons.
311              
312             =cut
313              
314             sub force_stop {
315 1     1 1 3 my ( $self ) = @_;
316              
317 1         8 for my $daemon ( $self->daemons() ) {
318 3         1865 print "forcefully stopping daemon: ", $daemon->daemon_name, "\n";
319 3         18 $daemon->force_stop();
320             }
321              
322 1         11 return 1;
323             }
324              
325             =item disable()
326              
327             Call the disable() method on all daemons.
328              
329             =cut
330              
331             sub disable {
332 1     1 1 1315 my ( $self ) = @_;
333              
334 1         6 for my $daemon ( $self->daemons() ) {
335 3         2279 print "disabling daemon: ", $daemon->daemon_name, "\n";
336 3         41 $daemon->disable();
337             }
338              
339 1         56 return 1;
340             }
341              
342             =item enable()
343              
344             Call the enable() method on all daemons.
345              
346             =cut
347              
348             sub enable {
349 1     1 1 8 my ( $self ) = @_;
350              
351 1         5 for my $daemon ( $self->daemons() ) {
352 3         361 print "enabling daemon: ", $daemon->daemon_name, "\n";
353 3         18 $daemon->enable();
354             }
355              
356 1         8 return 1;
357             }
358              
359             =item read_log()
360              
361             =cut
362              
363             sub read_log {
364 5     5 1 16 my ( $self, $output_callback, @daemons ) = @_;
365              
366 5 100       22 unless ( scalar @daemons ) { @daemons = $self->daemons() }
  1         6  
367              
368 5         16 for my $daemon ( @daemons ) {
369 4         21 $daemon->read_log( $output_callback );
370             }
371             }
372              
373             =item tail( $output_callback, $timeout_secs )
374              
375             Poll all daemons for output for the specified number of seconds. Any
376             output received in that time will be sent to $output_callback. Each
377             line of output will be prefixed with the daemon name.
378              
379              
380              
381             =cut
382              
383             sub tail {
384 2     2 1 6 my ( $self, $output_callback, $timeout ) = @_;
385              
386             # create an array of Log::Tail objects to be passed to select()
387 2         4 my @tails;
388              
389             # select() will return the paths associated with each daemon, so
390             # we need to be able to look up the name of a daemon based on it's
391             # log file path.
392             my %daemons;
393 2         21 for my $daemon ( $self->daemons() ) {
394 2         16 push @tails, $daemon->file_tail;
395 2         33 $daemons{ $daemon->log_file } = $daemon->daemon_name;
396             }
397              
398             # calculate the time when we're done processing.
399 2         14 my $end = time + $timeout;
400              
401             # display all new log output to stdout
402 2         6 my $count;
403 2         6 while ( 1 ) {
404              
405 8         38 my ($nfound,$timeleft,@pending)=
406             File::Tail::select(undef,undef,undef,1,@tails);
407              
408 8 100       4003601 if ($nfound) {
409 6         13 foreach (@pending) {
410 6   33     27 my $daemon_name = $daemons{ $_->{input} } || $_->{input};
411 6         31 my $text = $_->read;
412              
413 6         143 for my $line ( split /\n/, $text ) {
414 3         12 my $output = sprintf( "%-8s: %-1s", $daemon_name, $line );
415 3         14 $output_callback->( "$output\n" );
416             }
417             }
418             }
419             else {
420             # if we spawned any child procs, reap any that died
421 2         82 waitpid(-1, WNOHANG);
422 2         2000263 sleep 1;
423             }
424              
425             # if timeout was specified, quit when the timeout has passed.
426 8 50       62 if ( $timeout ) {
427 8 100       34 last if time > $end;
428             }
429             }
430              
431 2         57 return 1;
432             }
433              
434              
435              
436             =back
437              
438             =cut
439              
440 3     3   21 no Mouse;
  3         7  
  3         13  
441              
442             1;