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