package Proc::Launcher::Manager; use Mouse; our $VERSION = '0.0.1'; use Carp; use Proc::Launcher; =head1 NAME Proc::Launcher::Manager - spawn and manage multiple Proc::Launcher objects =head1 VERSION version 0.0.1 =head1 SYNOPSIS use Proc::Launcher::Manager; my $shared_config = { x => 1, y => 2 }; my $monitor = Proc::Launcher::Manager->new( app_name => 'MyApp', ); $monitor->spawn( daemon_name => 'component1', start_method => sub { MyApp->start_component1( $config ) } ); $monitor->spawn( daemon_name => 'component2', start_method => sub { MyApp->start_component2( $config ) } ); $monitor->spawn( daemon_name => 'webui', start_method => sub { MyApp->start_webui( $config ) } ); # start all registered daemons. processes that are already # running won't be restarted. $monitor->start_all(); # stop all daemons $monitor->stop_all(); sleep 1; $monitor->force_stop_all(); # get a specific daemon or perform actions on one (sorry demeter) my $webui = $monitor->daemon('webui'); $monitor->daemon('webui')->start(); # start the process supervisor. this will start up an event loop # and won't exit until it is killed. any processes not already # running will be started, and all processes will be monitored and # automatically restarted if they exit. $monitor->supervisor(); =head1 DESCRIPTION This library makes it easier to deal with multiple Proc::Launcher processes by providing methods to start and stop all daemons with a single command. Please see the documentation in Proc::Launcher to understand how this these libraries differ from other similar forking and controller modules. It also provides a supervisor() method which will spawn a child daemon that will monitor the other daemons at regular intervals and restart any that have stopped. Note that only one supervisor can be running at any given time for each pid_dir. A tail() method also exists that will spawn a POE::Wheel::FollowTail for each daemon's stdout/stderr log files and allow you to provide a callback to process the output. There is no tracking of inter-service dependencies nor predictable ordering of service startup. Instead, daemons should be designed to wait for needed resources. See Launcher::Cascade if you need to handle dependencies. =cut #_* Attributes has 'debug' => ( is => 'ro', isa => 'Bool', default => 0 ); has 'pid_dir' => ( is => 'ro', isa => 'Str', lazy => 1, default => sub { return join "/", $ENV{HOME}, ".proc", "logs"; }, ); has 'launchers' => ( is => 'rw', isa => 'HashRef[Proc::Launcher]', ); #_* Methods =head1 SUBROUTINES/METHODS =over 8 =item spawn( %options ) Create a new Proc::Launcher object with the specified options. If the specified daemon already exists, no changes will be made. =cut sub spawn { my ( $self, %options ) = @_; $options{pid_dir} = $self->pid_dir; unless ( $self->{daemons}->{ $options{daemon_name} } ) { $self->{daemons}->{ $options{daemon_name} } = Proc::Launcher->new( %options ); } } =item daemon( 'daemon_name' ) Return the Proc::Launcher object for a specified daemon. See: http://en.wikipedia.org/wiki/Law_of_Demeter =cut sub daemon { my ( $self, $daemon_name ) = @_; return $self->{daemons}->{ $daemon_name }; } =item daemons() Return a list of Proc::Launcher objects. =cut sub daemons { my ( $self ) = @_; my @daemons; for my $daemon_name ( $self->daemons_names() ) { push @daemons, $self->{daemons}->{ $daemon_name }; } return @daemons; } =item daemons_names() Return a list of the names of all registered daemons. =cut sub daemons_names { my ( $self ) = @_; return ( sort keys %{ $self->{daemons} } ); } =item daemons_running() Return a list of the names of daemons that are currently running. This will begin by calling rm_zombies() on the first daemon, sleeping a second, and then calling rm_zombies() again. So far the test cases have always passed when using this strategy, and inconsistently passed with any subset thereof. While it seems that this is more complicated than shutting down a single process, it's really just trying to be a bit more efficient. When managing a single process, is_running might be called a few times until the process exits. Since we might be managing a lot of daemons, this method is likely to be a bit more efficient and will hopefully only need to be called once (after the daemons have been given necessary time to shut down). =cut sub daemons_running { my ( $self ) = @_; my @daemon_names = $self->daemons_names(); # clean up deceased child processes before checking if processes # are running. $self->daemon($daemon_names[0])->rm_zombies(); # # give any processes that have ceased a second to shut down sleep 1; # again clean up deceased child processes before checking if # processes are running. $self->daemon($daemon_names[0])->rm_zombies(); my @running; for my $daemon_name ( @daemon_names ) { if ( $self->daemon($daemon_name)->is_running() ) { push @running, $daemon_name } } return @running; } =item start_all( $data ) Call the start() method on all daemons. If the daemon is already running it will not be restarted. =cut sub start_all { my ( $self, $data ) = @_; for my $daemon ( $self->daemons() ) { if ( $daemon->is_running() ) { print "daemon already running: ", $daemon->daemon_name, "\n"; } else { print "starting daemon: ", $daemon->daemon_name, "\n"; $daemon->start(); } } } =item stop_all() Call the stop() method on all daemons. =cut sub stop_all { my ( $self ) = @_; for my $daemon ( $self->daemons() ) { print "stopping daemon: ", $daemon->daemon_name, "\n"; $daemon->stop(); } return 1; } =item force_stop_all() Call the force_stop method on all daemons. =cut sub force_stop_all { my ( $self ) = @_; for my $daemon ( $self->daemons() ) { print "forcefully stopping daemon: ", $daemon->daemon_name, "\n"; $daemon->force_stop(); } return 1; } =item supervisor() Launch a supervisor process that will poll all selected daemons at regular intervals and restart them in the event that they fail. Only one supervisor can be running at a time. If you start a new supervisor process, it will replace the old one. =cut sub supervisor { my ( $self, $options ) = @_; $self->{supervisor} = Proc::Launcher->new( daemon_name => 'supervisor', pid_dir => $self->pid_dir, start_method => sub { require Proc::Launcher::Supervisor; Proc::Launcher::Supervisor->new()->monitor( $self ); }, ); print "Shutting down previously running supervisor\n"; $self->{supervisor}->force_stop(); sleep 1; print "Starting a new supervisor\n"; $self->{supervisor}->start(); } =item tail() =cut sub tail { my ( $self, $output_callback ) = @_; print "Tailing all log files\n"; require Proc::Launcher::Tail; Proc::Launcher::Tail->new( output_callback => $output_callback )->tail( $self ); } =back =cut 1;