From 26b064ed3eb0d5efe1c683c0527008971edc0105 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Fri, 10 Apr 2015 13:11:04 +1000 Subject: [PATCH 01/23] some syslog related documentation --- README.md | 90 ++++++++++++++++++++++++++++++++++++------- lib/Daemon/Control.pm | 58 ++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 966b61f..ec10d29 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Write a program that describes the daemon: use strict; use Daemon::Control; - Daemon::Control->new( + exit Daemon::Control->new( name => "My Daemon", lsb_start => '$syslog $remote_fs', lsb_stop => '$syslog', @@ -44,7 +44,7 @@ Write a program that describes the daemon: By default `run` will use @ARGV for the action, and exit with an LSB compatible exit code. For finer control, you can use `run_command`, which will return the exit code, and accepts the action as an argument. This enables more programatic -control, as well as running multiple instances of M from one script. +control, as well as running multiple instances of [Daemon::Control](https://metacpan.org/pod/Daemon::Control) from one script. my $daemon = Daemon::Control->new( ... @@ -59,10 +59,6 @@ You can also make an LSB compatible init script: /home/symkat/etc/init.d/program get_init_file > /etc/init.d/program - - - - # CONSTRUCTOR The constructor takes the following arguments as a list or a hash ref. @@ -185,7 +181,7 @@ and STDERR will be redirected to `stderr_file`. Setting this to 0 will disable redirecting before a double fork. This is useful when you are using a code reference and would like to leave the filehandles alone until you're in control. -Call `-`redirect\_filehandles> on the Daemon::Control instance your coderef is +Call `->redirect_filehandles` on the Daemon::Control instance your coderef is passed to redirect the filehandles. ## stdout\_file @@ -253,7 +249,6 @@ as that the daemon started. A shortcut to turn status off and go into foregroun mode is `foreground` being set to 1, or `DC_FOREGROUND` being set as an environment variable. Additionally, calling `foreground` instead of `start` will override the forking mode at run-time. - $daemon->fork( 0 ); @@ -317,13 +312,25 @@ If this boolean flag is set to a true value all output from the init script $daemon->quiet( 1 ); +## reload\_signal + +The signal to send to the daemon when reloading it. +Default signal is `HUP`. + +## stop\_signals + +An array ref of signals that should be tried (in order) when +stopping the daemon. +Default signals are `TERM`, `TERM`, `INT` and `KILL` (yes, `TERM` +is tried twice). + # METHODS ## run\_command This function will process an action on the Daemon::Control instance. Valid arguments are those which a `do_` method exists for, such as -__start__, __stop__, __restart__. Returns the LSB exit code for the +**start**, **stop**, **restart**. Returns the LSB exit code for the action processed. ## run @@ -344,10 +351,10 @@ exits. Called by: ## do\_foreground -Is called when __foreground__ is given as an argument. Starts the +Is called when **foreground** is given as an argument. Starts the program or code reference and stays in the foreground -- no forking is done, regardless of the compile-time arguments. Additionally, -turns `quiet` on to avoid showing M output. +turns `quiet` on to avoid showing [Daemon::Control](https://metacpan.org/pod/Daemon::Control) output. /usr/bin/my_program_launcher.pl foreground @@ -367,8 +374,8 @@ Called by: ## do\_reload -Is called when reload is given as an argument. Sends a HUP signal to the -daemon. +Is called when reload is given as an argument. Sends the signal +`reload_signal` to the daemon. /usr/bin/my_program_launcher.pl reload @@ -410,9 +417,62 @@ An accessor for the PID. Set by read\_pid, or when the program is started. A function to dump the LSB compatible init script. Used by do\_get\_init\_file. +# FAQ + +## LOGGING TO SYSLOG + +Logging a daemon::control script to syslog can be a little involved. +If you're using Log4perl or similar, consider using +[Log::Dispatch::Syslog](https://metacpan.org/pod/Log::Dispatch::Syslog) and/or [Sys::Syslog](https://metacpan.org/pod/Sys::Syslog). An alternative +approach using a fifo is as follows: + +First, set up the stderr\_file and stdout\_file to a fifo. + +Daemon::Control->new({ + ..., # normal setup + stderr\_file => "/var/log/myuser/myservice.fifo", + stdout\_file => "/var/log/myuser/myservice.fifo", + ..., })->run; + +However you need a service running that reads from the fifo, in this +case logger(1). When your main service (that writes to the fifos) exits +this close is read by ` logger ` and causes it to exit. In order to avoid +that we created a service that respawns logger when it dies. This +example is for a redhat system running upstart: + +The following script can be dropped into /etc/init as fifo-logger.conf +And then started with ` initctl start fifo-logger `. + + # fifo-logger - logger process for fcgi + # + # Will respawn the logger process as it exits on file close + + start on stopped rc RUNLEVEL=[345] + + stop on starting shutdown + + console output + respawn + + script + echo $$ > /var/run/fifo-logger.pid + exec logger -f /var/log/myuser/myservice.fifo -t myservice -p local0.notice + end script + + pre-start script + if [ ! -e /var/log/myuser/myservice.fifo ]; then + mkfifo /var/log/myuser/myservice.fifo + fi + chown hiive.hiive /var/log/myuser/myservice.fifo + chmod 660 /var/log/myuser/myservice.fifo + end script + +From this point, all the output will be sent to syslog as local0.notice and can +then be routed/cycled a needed without requiring a restart of the application. + # AUTHOR - Kaitlyn Parkhurst (SymKat) __ ( Blog: [http://symkat.com/](http://symkat.com/) ) +> Kaitlyn Parkhurst (SymKat) __ ( Blog: [http://symkat.com/](http://symkat.com/) ) ## CONTRIBUTORS @@ -420,6 +480,8 @@ A function to dump the LSB compatible init script. Used by do\_get\_init\_file. - Mike Doherty (doherty) __ - Karen Etheridge (ether) __ - Ævar Arnfjörð Bjarmason (avar) __ +- Kieren Diment _ +- Mark Curtis _ ## SPONSORS diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 2d3dbb3..4437d5c 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -1112,6 +1112,60 @@ An accessor for the PID. Set by read_pid, or when the program is started. A function to dump the LSB compatible init script. Used by do_get_init_file. +=head1 FAQ + +=head2 LOGGING TO SYSLOG + +Logging a daemon::control script to syslog can be a little involved. +If you're using Log4perl or similar, consider using +L and/or L. An alternative +approach using a fifo is as follows: + +First, set up the stderr_file and stdout_file to a fifo. + +Daemon::Control->new({ + ..., # normal setup + stderr_file => "/var/log/myuser/myservice.fifo", + stdout_file => "/var/log/myuser/myservice.fifo", + ..., })->run; + +However you need a service running that reads from the fifo, in this +case logger(1). When your main service (that writes to the fifos) exits +this close is read by C< logger > and causes it to exit. In order to avoid +that we created a service that respawns logger when it dies. This +example is for a redhat system running upstart: + +The following script can be dropped into /etc/init as fifo-logger.conf +And then started with C< initctl start fifo-logger >. + + # fifo-logger - logger process for fcgi + # + # Will respawn the logger process as it exits on file close + + start on stopped rc RUNLEVEL=[345] + + stop on starting shutdown + + console output + respawn + + script + echo $$ > /var/run/fifo-logger.pid + exec logger -f /var/log/myuser/myservice.fifo -t myservice -p local0.notice + end script + + pre-start script + if [ ! -e /var/log/myuser/myservice.fifo ]; then + mkfifo /var/log/myuser/myservice.fifo + fi + chown hiive.hiive /var/log/myuser/myservice.fifo + chmod 660 /var/log/myuser/myservice.fifo + end script + +From this point, all the output will be sent to syslog as local0.notice and can +then be routed/cycled a needed without requiring a restart of the application. + + =head1 AUTHOR =over 4 @@ -1132,6 +1186,10 @@ Kaitlyn Parkhurst (SymKat) Isymkat@symkat.comE> ( Blog: Lavar@cpan.orgE> +=item * Kieren Diment Izarquon@cpan.org> + +=item * Mark Curtis Imark.curtis@affinitylive.com> + =back =head2 SPONSORS From 0f7ef43c3fb33ec2dbffe72b68af6c934bbff182 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Fri, 10 Apr 2015 13:13:45 +1000 Subject: [PATCH 02/23] markdown formatting error --- README.md | 10 +++++----- lib/Daemon/Control.pm | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ec10d29..fbd8b99 100644 --- a/README.md +++ b/README.md @@ -428,11 +428,11 @@ approach using a fifo is as follows: First, set up the stderr\_file and stdout\_file to a fifo. -Daemon::Control->new({ - ..., # normal setup - stderr\_file => "/var/log/myuser/myservice.fifo", - stdout\_file => "/var/log/myuser/myservice.fifo", - ..., })->run; + Daemon::Control->new({ + ..., # normal setup + stderr_file => "/var/log/myuser/myservice.fifo", + stdout_file => "/var/log/myuser/myservice.fifo", + ..., })->run; However you need a service running that reads from the fifo, in this case logger(1). When your main service (that writes to the fifos) exits diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 4437d5c..9d84dbf 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -1123,11 +1123,11 @@ approach using a fifo is as follows: First, set up the stderr_file and stdout_file to a fifo. -Daemon::Control->new({ - ..., # normal setup - stderr_file => "/var/log/myuser/myservice.fifo", - stdout_file => "/var/log/myuser/myservice.fifo", - ..., })->run; + Daemon::Control->new({ + ..., # normal setup + stderr_file => "/var/log/myuser/myservice.fifo", + stdout_file => "/var/log/myuser/myservice.fifo", + ..., })->run; However you need a service running that reads from the fifo, in this case logger(1). When your main service (that writes to the fifos) exits From 0d38226ba572a9344b2578483c31f8e4bab896ab Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Thu, 16 Jul 2015 15:43:26 +1000 Subject: [PATCH 03/23] naive implementation of plugin system and hot standby --- lib/Daemon/Control.pm | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 9d84dbf..badb4ae 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -17,6 +17,7 @@ my @accessors = qw( lsb_start lsb_stop lsb_sdesc lsb_desc redirect_before_fork init_config kill_timeout umask resource_dir help init_code prereq_no_process foreground reload_signal stop_signals + with_plugins ); my $cmd_opt = "[start|stop|restart|reload|status|foreground|show_warnings|get_init_file|help]"; @@ -83,7 +84,6 @@ sub gid { sub new { my ( $class, @in ) = @_; - my $args = ref $in[0] eq 'HASH' ? $in[0] : { @in }; # Create the object with defaults. @@ -109,6 +109,9 @@ sub new { $self->fork( 0 ); $self->quiet( 1 ); } + my @plugins = $self->_maybe_find_plugins; + $DB::single=1; + $self->_register_plugins(@plugins) if @plugins; die "Unknown arguments to the constructor: " . join( " ", keys %$args ) if keys( %$args ); @@ -116,6 +119,42 @@ sub new { return $self; } +sub _maybe_find_plugins { + my ($self) = @_; + my @plugins; + if ($self->with_plugins) { + my $plugins = ref($self->with_plugins) + ? $self->with_plugins + : [$self->with_plugins]; + @plugins = @$plugins; + } + return @plugins; +} + +sub _register_plugins { + my ($self, @plugins) = @_; + my @plugin_list; + foreach my $plugin (@plugins) { + if ($plugin =~ /::/) { # Fully qualified namespace for plugin. + push @plugin_list, $plugin; + } + else { # Assumed to come off Daemon::Control::Plugin::Namespace; + my @name = split '_', $plugin; + $_ = ucfirst($_) for @name; + my $name = join '', @name; + $name =~ s/-/::/; + my $plugin_role = ref($self) . '::Plugin::' . $name; + push @plugin_list, $plugin_role; + } + } + require Role::Tiny; + if ( @plugin_list ) { + Role::Tiny->apply_roles_to_object($self, @plugin_list); + } + else { + warn "Trying to apply plugins to " . ref($self) . " but plugin list is entry"; + } +} # Set the uid, triggered from getting the uid if the user has changed. sub _set_uid_from_name { From cdcb32c7b2dfe92f41383a023ab1f88d5ac8106e Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Thu, 16 Jul 2015 15:44:08 +1000 Subject: [PATCH 04/23] an underengineered failing test case --- t/bin/10-hot_standby.pl | 28 ++++++++++++++++++++++++++++ t/bin/10-hot_standby_daemon.sh | 2 ++ 2 files changed, 30 insertions(+) create mode 100644 t/bin/10-hot_standby.pl create mode 100755 t/bin/10-hot_standby_daemon.sh diff --git a/t/bin/10-hot_standby.pl b/t/bin/10-hot_standby.pl new file mode 100644 index 0000000..4036787 --- /dev/null +++ b/t/bin/10-hot_standby.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Daemon::Control; + +my ($path) = $0 =~ m{(.*/)}; +my $script = $path . '10-hot_standby_daemon.sh'; + +Daemon::Control->new({ + name => "My Daemon", + with_plugins => 'hot_standby', + lsb_start => '$syslog $remote_fs', + lsb_stop => '$syslog', + lsb_sdesc => 'My Daemon Short', + lsb_desc => 'My Daemon controls the My Daemon daemon.', + path => '/usr/sbin/mydaemon/init.pl', + + program => $script, + program_args => [ 10 ], + + pid_file => '/tmp/hotstandby_pid', + + stderr_file => '/tmp/test_hot_standby_err', + stdout_file => '/tmp/test_hot_standby_out', + + fork => 2, + +})->run; diff --git a/t/bin/10-hot_standby_daemon.sh b/t/bin/10-hot_standby_daemon.sh new file mode 100755 index 0000000..44a712b --- /dev/null +++ b/t/bin/10-hot_standby_daemon.sh @@ -0,0 +1,2 @@ +#!/bin/sh +while true; do echo $$; sleep 1; done From 10a813fcd6769663248fd9e333c1a705bb18e73c Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Fri, 10 Apr 2015 13:11:04 +1000 Subject: [PATCH 05/23] some syslog related documentation --- README.md | 90 ++++++++++++++++++++++++++++++++++++------- lib/Daemon/Control.pm | 58 ++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 966b61f..ec10d29 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Write a program that describes the daemon: use strict; use Daemon::Control; - Daemon::Control->new( + exit Daemon::Control->new( name => "My Daemon", lsb_start => '$syslog $remote_fs', lsb_stop => '$syslog', @@ -44,7 +44,7 @@ Write a program that describes the daemon: By default `run` will use @ARGV for the action, and exit with an LSB compatible exit code. For finer control, you can use `run_command`, which will return the exit code, and accepts the action as an argument. This enables more programatic -control, as well as running multiple instances of M from one script. +control, as well as running multiple instances of [Daemon::Control](https://metacpan.org/pod/Daemon::Control) from one script. my $daemon = Daemon::Control->new( ... @@ -59,10 +59,6 @@ You can also make an LSB compatible init script: /home/symkat/etc/init.d/program get_init_file > /etc/init.d/program - - - - # CONSTRUCTOR The constructor takes the following arguments as a list or a hash ref. @@ -185,7 +181,7 @@ and STDERR will be redirected to `stderr_file`. Setting this to 0 will disable redirecting before a double fork. This is useful when you are using a code reference and would like to leave the filehandles alone until you're in control. -Call `-`redirect\_filehandles> on the Daemon::Control instance your coderef is +Call `->redirect_filehandles` on the Daemon::Control instance your coderef is passed to redirect the filehandles. ## stdout\_file @@ -253,7 +249,6 @@ as that the daemon started. A shortcut to turn status off and go into foregroun mode is `foreground` being set to 1, or `DC_FOREGROUND` being set as an environment variable. Additionally, calling `foreground` instead of `start` will override the forking mode at run-time. - $daemon->fork( 0 ); @@ -317,13 +312,25 @@ If this boolean flag is set to a true value all output from the init script $daemon->quiet( 1 ); +## reload\_signal + +The signal to send to the daemon when reloading it. +Default signal is `HUP`. + +## stop\_signals + +An array ref of signals that should be tried (in order) when +stopping the daemon. +Default signals are `TERM`, `TERM`, `INT` and `KILL` (yes, `TERM` +is tried twice). + # METHODS ## run\_command This function will process an action on the Daemon::Control instance. Valid arguments are those which a `do_` method exists for, such as -__start__, __stop__, __restart__. Returns the LSB exit code for the +**start**, **stop**, **restart**. Returns the LSB exit code for the action processed. ## run @@ -344,10 +351,10 @@ exits. Called by: ## do\_foreground -Is called when __foreground__ is given as an argument. Starts the +Is called when **foreground** is given as an argument. Starts the program or code reference and stays in the foreground -- no forking is done, regardless of the compile-time arguments. Additionally, -turns `quiet` on to avoid showing M output. +turns `quiet` on to avoid showing [Daemon::Control](https://metacpan.org/pod/Daemon::Control) output. /usr/bin/my_program_launcher.pl foreground @@ -367,8 +374,8 @@ Called by: ## do\_reload -Is called when reload is given as an argument. Sends a HUP signal to the -daemon. +Is called when reload is given as an argument. Sends the signal +`reload_signal` to the daemon. /usr/bin/my_program_launcher.pl reload @@ -410,9 +417,62 @@ An accessor for the PID. Set by read\_pid, or when the program is started. A function to dump the LSB compatible init script. Used by do\_get\_init\_file. +# FAQ + +## LOGGING TO SYSLOG + +Logging a daemon::control script to syslog can be a little involved. +If you're using Log4perl or similar, consider using +[Log::Dispatch::Syslog](https://metacpan.org/pod/Log::Dispatch::Syslog) and/or [Sys::Syslog](https://metacpan.org/pod/Sys::Syslog). An alternative +approach using a fifo is as follows: + +First, set up the stderr\_file and stdout\_file to a fifo. + +Daemon::Control->new({ + ..., # normal setup + stderr\_file => "/var/log/myuser/myservice.fifo", + stdout\_file => "/var/log/myuser/myservice.fifo", + ..., })->run; + +However you need a service running that reads from the fifo, in this +case logger(1). When your main service (that writes to the fifos) exits +this close is read by ` logger ` and causes it to exit. In order to avoid +that we created a service that respawns logger when it dies. This +example is for a redhat system running upstart: + +The following script can be dropped into /etc/init as fifo-logger.conf +And then started with ` initctl start fifo-logger `. + + # fifo-logger - logger process for fcgi + # + # Will respawn the logger process as it exits on file close + + start on stopped rc RUNLEVEL=[345] + + stop on starting shutdown + + console output + respawn + + script + echo $$ > /var/run/fifo-logger.pid + exec logger -f /var/log/myuser/myservice.fifo -t myservice -p local0.notice + end script + + pre-start script + if [ ! -e /var/log/myuser/myservice.fifo ]; then + mkfifo /var/log/myuser/myservice.fifo + fi + chown hiive.hiive /var/log/myuser/myservice.fifo + chmod 660 /var/log/myuser/myservice.fifo + end script + +From this point, all the output will be sent to syslog as local0.notice and can +then be routed/cycled a needed without requiring a restart of the application. + # AUTHOR - Kaitlyn Parkhurst (SymKat) __ ( Blog: [http://symkat.com/](http://symkat.com/) ) +> Kaitlyn Parkhurst (SymKat) __ ( Blog: [http://symkat.com/](http://symkat.com/) ) ## CONTRIBUTORS @@ -420,6 +480,8 @@ A function to dump the LSB compatible init script. Used by do\_get\_init\_file. - Mike Doherty (doherty) __ - Karen Etheridge (ether) __ - Ævar Arnfjörð Bjarmason (avar) __ +- Kieren Diment _ +- Mark Curtis _ ## SPONSORS diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index fe1c7ad..dc01a36 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -1112,6 +1112,60 @@ An accessor for the PID. Set by read_pid, or when the program is started. A function to dump the LSB compatible init script. Used by do_get_init_file. +=head1 FAQ + +=head2 LOGGING TO SYSLOG + +Logging a daemon::control script to syslog can be a little involved. +If you're using Log4perl or similar, consider using +L and/or L. An alternative +approach using a fifo is as follows: + +First, set up the stderr_file and stdout_file to a fifo. + +Daemon::Control->new({ + ..., # normal setup + stderr_file => "/var/log/myuser/myservice.fifo", + stdout_file => "/var/log/myuser/myservice.fifo", + ..., })->run; + +However you need a service running that reads from the fifo, in this +case logger(1). When your main service (that writes to the fifos) exits +this close is read by C< logger > and causes it to exit. In order to avoid +that we created a service that respawns logger when it dies. This +example is for a redhat system running upstart: + +The following script can be dropped into /etc/init as fifo-logger.conf +And then started with C< initctl start fifo-logger >. + + # fifo-logger - logger process for fcgi + # + # Will respawn the logger process as it exits on file close + + start on stopped rc RUNLEVEL=[345] + + stop on starting shutdown + + console output + respawn + + script + echo $$ > /var/run/fifo-logger.pid + exec logger -f /var/log/myuser/myservice.fifo -t myservice -p local0.notice + end script + + pre-start script + if [ ! -e /var/log/myuser/myservice.fifo ]; then + mkfifo /var/log/myuser/myservice.fifo + fi + chown hiive.hiive /var/log/myuser/myservice.fifo + chmod 660 /var/log/myuser/myservice.fifo + end script + +From this point, all the output will be sent to syslog as local0.notice and can +then be routed/cycled a needed without requiring a restart of the application. + + =head1 AUTHOR =over 4 @@ -1132,6 +1186,10 @@ Kaitlyn Parkhurst (SymKat) Isymkat@symkat.comE> ( Blog: Lavar@cpan.orgE> +=item * Kieren Diment Izarquon@cpan.org> + +=item * Mark Curtis Imark.curtis@affinitylive.com> + =back =head2 SPONSORS From a3a3da0b0a15742c46c75852c4c8b5e507539bab Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Fri, 10 Apr 2015 13:13:45 +1000 Subject: [PATCH 06/23] markdown formatting error --- README.md | 10 +++++----- lib/Daemon/Control.pm | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ec10d29..fbd8b99 100644 --- a/README.md +++ b/README.md @@ -428,11 +428,11 @@ approach using a fifo is as follows: First, set up the stderr\_file and stdout\_file to a fifo. -Daemon::Control->new({ - ..., # normal setup - stderr\_file => "/var/log/myuser/myservice.fifo", - stdout\_file => "/var/log/myuser/myservice.fifo", - ..., })->run; + Daemon::Control->new({ + ..., # normal setup + stderr_file => "/var/log/myuser/myservice.fifo", + stdout_file => "/var/log/myuser/myservice.fifo", + ..., })->run; However you need a service running that reads from the fifo, in this case logger(1). When your main service (that writes to the fifos) exits diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index dc01a36..5d35931 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -1123,11 +1123,11 @@ approach using a fifo is as follows: First, set up the stderr_file and stdout_file to a fifo. -Daemon::Control->new({ - ..., # normal setup - stderr_file => "/var/log/myuser/myservice.fifo", - stdout_file => "/var/log/myuser/myservice.fifo", - ..., })->run; + Daemon::Control->new({ + ..., # normal setup + stderr_file => "/var/log/myuser/myservice.fifo", + stdout_file => "/var/log/myuser/myservice.fifo", + ..., })->run; However you need a service running that reads from the fifo, in this case logger(1). When your main service (that writes to the fifos) exits From bfd1f9424806a8176b28b69ded2c1ea669f099c9 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Thu, 16 Jul 2015 15:43:26 +1000 Subject: [PATCH 07/23] naive implementation of plugin system and hot standby --- lib/Daemon/Control.pm | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 5d35931..fc51011 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -17,6 +17,7 @@ my @accessors = qw( lsb_start lsb_stop lsb_sdesc lsb_desc redirect_before_fork init_config kill_timeout umask resource_dir help init_code prereq_no_process foreground reload_signal stop_signals + with_plugins ); my $cmd_opt = "[start|stop|restart|reload|status|foreground|show_warnings|get_init_file|help]"; @@ -83,7 +84,6 @@ sub gid { sub new { my ( $class, @in ) = @_; - my $args = ref $in[0] eq 'HASH' ? $in[0] : { @in }; # Create the object with defaults. @@ -109,6 +109,9 @@ sub new { $self->fork( 0 ); $self->quiet( 1 ); } + my @plugins = $self->_maybe_find_plugins; + $DB::single=1; + $self->_register_plugins(@plugins) if @plugins; die "Unknown arguments to the constructor: " . join( " ", keys %$args ) if keys( %$args ); @@ -116,6 +119,42 @@ sub new { return $self; } +sub _maybe_find_plugins { + my ($self) = @_; + my @plugins; + if ($self->with_plugins) { + my $plugins = ref($self->with_plugins) + ? $self->with_plugins + : [$self->with_plugins]; + @plugins = @$plugins; + } + return @plugins; +} + +sub _register_plugins { + my ($self, @plugins) = @_; + my @plugin_list; + foreach my $plugin (@plugins) { + if ($plugin =~ /::/) { # Fully qualified namespace for plugin. + push @plugin_list, $plugin; + } + else { # Assumed to come off Daemon::Control::Plugin::Namespace; + my @name = split '_', $plugin; + $_ = ucfirst($_) for @name; + my $name = join '', @name; + $name =~ s/-/::/; + my $plugin_role = ref($self) . '::Plugin::' . $name; + push @plugin_list, $plugin_role; + } + } + require Role::Tiny; + if ( @plugin_list ) { + Role::Tiny->apply_roles_to_object($self, @plugin_list); + } + else { + warn "Trying to apply plugins to " . ref($self) . " but plugin list is entry"; + } +} # Set the uid, triggered from getting the uid if the user has changed. sub _set_uid_from_name { From 5f6846d950753fd408d70dc092bffbde4d2321cd Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Thu, 16 Jul 2015 15:44:08 +1000 Subject: [PATCH 08/23] an underengineered failing test case --- t/bin/10-hot_standby.pl | 28 ++++++++++++++++++++++++++++ t/bin/10-hot_standby_daemon.sh | 2 ++ 2 files changed, 30 insertions(+) create mode 100644 t/bin/10-hot_standby.pl create mode 100755 t/bin/10-hot_standby_daemon.sh diff --git a/t/bin/10-hot_standby.pl b/t/bin/10-hot_standby.pl new file mode 100644 index 0000000..4036787 --- /dev/null +++ b/t/bin/10-hot_standby.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Daemon::Control; + +my ($path) = $0 =~ m{(.*/)}; +my $script = $path . '10-hot_standby_daemon.sh'; + +Daemon::Control->new({ + name => "My Daemon", + with_plugins => 'hot_standby', + lsb_start => '$syslog $remote_fs', + lsb_stop => '$syslog', + lsb_sdesc => 'My Daemon Short', + lsb_desc => 'My Daemon controls the My Daemon daemon.', + path => '/usr/sbin/mydaemon/init.pl', + + program => $script, + program_args => [ 10 ], + + pid_file => '/tmp/hotstandby_pid', + + stderr_file => '/tmp/test_hot_standby_err', + stdout_file => '/tmp/test_hot_standby_out', + + fork => 2, + +})->run; diff --git a/t/bin/10-hot_standby_daemon.sh b/t/bin/10-hot_standby_daemon.sh new file mode 100755 index 0000000..44a712b --- /dev/null +++ b/t/bin/10-hot_standby_daemon.sh @@ -0,0 +1,2 @@ +#!/bin/sh +while true; do echo $$; sleep 1; done From 0e39668e3606830e6d6c80828672accf286b45bd Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Thu, 16 Jul 2015 16:27:49 +1000 Subject: [PATCH 09/23] wee bit of a refactor for hot_standby purposes --- lib/Daemon/Control.pm | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index fc51011..4871321 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -457,6 +457,21 @@ sub do_start { my ( $self ) = @_; # Optionally check if a process is already running with the same name + my $prereq_check = $self->_check_prereq_no_processes; + return $prereq_check if $prereq_check; + + # Make sure the PID file exists. + $self->_check_pid_file_exists(); + + # Duplicate Check + my $is_duplicate = $self->_check_for_duplicate(); + return $is_duplicate if $is_duplicate; + + return $self->_finish_start(); +} + +sub _check_prereq_no_processes { + my ($self) = @_; if ($self->prereq_no_process) { my $program = $self->program; @@ -470,20 +485,27 @@ sub do_start { return 1; } } +} - # Make sure the PID file exists. +sub _check_pid_file_exists { + my ($self) = @_; if ( ! -f $self->pid_file ) { $self->pid( 0 ); # Make PID invalid. $self->write_pid(); } +} - # Duplicate Check +sub _check_for_duplicate { + my ($self) = @_; $self->read_pid; if ( $self->pid && $self->pid_running ) { $self->pretty_print( "Duplicate Running", "red" ); return 1; } +} +sub _finish_start { + my ($self) = @_; $self->_create_resource_dir; $self->fork( 2 ) unless defined $self->fork; @@ -492,6 +514,7 @@ sub do_start { $self->_foreground if $self->fork == 0; $self->pretty_print( "Started" ); return 0; + } sub do_show_warnings { From 13ecfa01ad86fc6c1c10e6c45f0175b637633c5e Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Tue, 21 Jul 2015 10:43:19 +1000 Subject: [PATCH 10/23] docs --- lib/Daemon/Control.pm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 4871321..6f7e42c 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -1081,6 +1081,19 @@ stopping the daemon. Default signals are C, C, C and C (yes, C is tried twice). +=head2 with_plugins + +A string or an arrayref of Daemon::Control plugins to apply. Each entry can be in the form of a fully qualified namespace, or a string assumed to be in the C namespace. For example: + + with_plugins => hot_standby, some-other_plugin SomePlugin NameSpace::For::MyCustomPlugin + +This will apply the plugins L, +C +C and +C in that order. Plugins are made +with Role::Tiny, and uses method modifiers to selectively change the +main library behaviour. + =head1 METHODS =head2 run_command From 28a99cf4e4aad68d5935f45bf6b21024b1c785a8 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Tue, 21 Jul 2015 10:55:31 +1000 Subject: [PATCH 11/23] adjust sub naming --- lib/Daemon/Control.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 6f7e42c..35a0d8f 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -461,7 +461,7 @@ sub do_start { return $prereq_check if $prereq_check; # Make sure the PID file exists. - $self->_check_pid_file_exists(); + $self->_ensure_pid_file_exists(); # Duplicate Check my $is_duplicate = $self->_check_for_duplicate(); @@ -487,7 +487,7 @@ sub _check_prereq_no_processes { } } -sub _check_pid_file_exists { +sub _ensure_pid_file_exists { my ($self) = @_; if ( ! -f $self->pid_file ) { $self->pid( 0 ); # Make PID invalid. From c8d16ab4ead18cb71f2b66e9ef5530e78c8ac6c9 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Tue, 21 Jul 2015 12:10:20 +1000 Subject: [PATCH 12/23] Better class based process for adding plugins --- lib/Daemon/Control.pm | 78 ++++++++++++++++------------------------- t/bin/10-hot_standby.pl | 3 +- 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 35a0d8f..b79ebb8 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -17,7 +17,7 @@ my @accessors = qw( lsb_start lsb_stop lsb_sdesc lsb_desc redirect_before_fork init_config kill_timeout umask resource_dir help init_code prereq_no_process foreground reload_signal stop_signals - with_plugins + plugins ); my $cmd_opt = "[start|stop|restart|reload|status|foreground|show_warnings|get_init_file|help]"; @@ -84,7 +84,13 @@ sub gid { sub new { my ( $class, @in ) = @_; + my $args = ref $in[0] eq 'HASH' ? $in[0] : { @in }; + my @plugins = _maybe_find_plugins($args->{plugins}); + require Role::Tiny if @plugins; + if (@plugins) { + $class = Role::Tiny->create_class_with_roles($class, @plugins); + } # Create the object with defaults. my $self = bless { @@ -109,9 +115,6 @@ sub new { $self->fork( 0 ); $self->quiet( 1 ); } - my @plugins = $self->_maybe_find_plugins; - $DB::single=1; - $self->_register_plugins(@plugins) if @plugins; die "Unknown arguments to the constructor: " . join( " ", keys %$args ) if keys( %$args ); @@ -120,40 +123,15 @@ sub new { } sub _maybe_find_plugins { - my ($self) = @_; - my @plugins; - if ($self->with_plugins) { - my $plugins = ref($self->with_plugins) - ? $self->with_plugins - : [$self->with_plugins]; - @plugins = @$plugins; - } - return @plugins; -} - -sub _register_plugins { - my ($self, @plugins) = @_; - my @plugin_list; - foreach my $plugin (@plugins) { - if ($plugin =~ /::/) { # Fully qualified namespace for plugin. - push @plugin_list, $plugin; - } - else { # Assumed to come off Daemon::Control::Plugin::Namespace; - my @name = split '_', $plugin; - $_ = ucfirst($_) for @name; - my $name = join '', @name; - $name =~ s/-/::/; - my $plugin_role = ref($self) . '::Plugin::' . $name; - push @plugin_list, $plugin_role; - } - } - require Role::Tiny; - if ( @plugin_list ) { - Role::Tiny->apply_roles_to_object($self, @plugin_list); - } - else { - warn "Trying to apply plugins to " . ref($self) . " but plugin list is entry"; - } + my ($plugins) = @_; + return () if ! $plugins; + my @plugins = ref $plugins ? @$plugins : ($plugins); + + @plugins = map { + my ($fqns, $name) = $_ =~ /^(\+?)(.*?)$/; + $_ = "Daemon::Control::Plugin::$name" unless $fqns; + } @plugins; + return @plugins; } # Set the uid, triggered from getting the uid if the user has changed. @@ -1081,18 +1059,24 @@ stopping the daemon. Default signals are C, C, C and C (yes, C is tried twice). -=head2 with_plugins +=head2 plugins + +A string or an arrayref of Daemon::Control plugins to apply. Each +entry can be in the form of a fully qualified namespace prepended by a +C<+>, or a string assumed to be in the C +namespace. For example: + + plugins => 'HotStandby' + +will load the L plugin + + plugins => '+My::Plugin' -A string or an arrayref of Daemon::Control plugins to apply. Each entry can be in the form of a fully qualified namespace, or a string assumed to be in the C namespace. For example: +will load the C< My::Plugin > plugin - with_plugins => hot_standby, some-other_plugin SomePlugin NameSpace::For::MyCustomPlugin + plugins => [ qw/+My::Plugin HotStandby/ ] -This will apply the plugins L, -C -C and -C in that order. Plugins are made -with Role::Tiny, and uses method modifiers to selectively change the -main library behaviour. +will load both. =head1 METHODS diff --git a/t/bin/10-hot_standby.pl b/t/bin/10-hot_standby.pl index 4036787..9dfde1b 100644 --- a/t/bin/10-hot_standby.pl +++ b/t/bin/10-hot_standby.pl @@ -5,10 +5,9 @@ my ($path) = $0 =~ m{(.*/)}; my $script = $path . '10-hot_standby_daemon.sh'; - Daemon::Control->new({ name => "My Daemon", - with_plugins => 'hot_standby', + plugins => 'HotStandby', lsb_start => '$syslog $remote_fs', lsb_stop => '$syslog', lsb_sdesc => 'My Daemon Short', From 7fd904139aac7add0a55329fa04b40799aaf4473 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Tue, 21 Jul 2015 12:42:54 +1000 Subject: [PATCH 13/23] make plugin application less retarded --- lib/Daemon/Control.pm | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index b79ebb8..bfdd48d 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -86,11 +86,7 @@ sub new { my ( $class, @in ) = @_; my $args = ref $in[0] eq 'HASH' ? $in[0] : { @in }; - my @plugins = _maybe_find_plugins($args->{plugins}); - require Role::Tiny if @plugins; - if (@plugins) { - $class = Role::Tiny->create_class_with_roles($class, @plugins); - } + $class = $class->apply_plugins($args->{plugins}); # Create the object with defaults. my $self = bless { @@ -122,16 +118,22 @@ sub new { return $self; } -sub _maybe_find_plugins { - my ($plugins) = @_; - return () if ! $plugins; +sub apply_plugins { + my ($class, $plugins) = @_; + $plugins ||= (); my @plugins = ref $plugins ? @$plugins : ($plugins); + return $class if ! $plugins; @plugins = map { my ($fqns, $name) = $_ =~ /^(\+?)(.*?)$/; $_ = "Daemon::Control::Plugin::$name" unless $fqns; } @plugins; - return @plugins; + require Role::Tiny if @plugins; + if (@plugins) { + $class = Role::Tiny->create_class_with_roles($class, @plugins); + } + + return $class; } # Set the uid, triggered from getting the uid if the user has changed. From 14d6272e052ab26abab4ab3baa0a7922f2a6fcf4 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Tue, 21 Jul 2015 12:48:20 +1000 Subject: [PATCH 14/23] with_plugins, not apply_plugins as it's a class method not an object method --- lib/Daemon/Control.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index bfdd48d..d335d31 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -86,7 +86,7 @@ sub new { my ( $class, @in ) = @_; my $args = ref $in[0] eq 'HASH' ? $in[0] : { @in }; - $class = $class->apply_plugins($args->{plugins}); + $class = $class->with_plugins($args->{plugins}); # Create the object with defaults. my $self = bless { @@ -118,7 +118,7 @@ sub new { return $self; } -sub apply_plugins { +sub with_plugins { my ($class, $plugins) = @_; $plugins ||= (); my @plugins = ref $plugins ? @$plugins : ($plugins); From 5583a0d58b1d98d3830f69a9d2b58d19871931ba Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Tue, 21 Jul 2015 13:04:48 +1000 Subject: [PATCH 15/23] test plugin system --- lib/Daemon/Control.pm | 4 ++-- t/08-null_plugin.t | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 t/08-null_plugin.t diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index d335d31..10b7954 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -125,8 +125,8 @@ sub with_plugins { return $class if ! $plugins; @plugins = map { - my ($fqns, $name) = $_ =~ /^(\+?)(.*?)$/; - $_ = "Daemon::Control::Plugin::$name" unless $fqns; + my ($fqns, $name) = $_ =~ /^(\+)?(.*?)$/; + $_ = $fqns ? $name : "Daemon::Control::Plugin::$name"; } @plugins; require Role::Tiny if @plugins; if (@plugins) { diff --git a/t/08-null_plugin.t b/t/08-null_plugin.t new file mode 100644 index 0000000..77a19de --- /dev/null +++ b/t/08-null_plugin.t @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +use warnings; +use strict; +package Daemon::Control::Plugin::Null; +use Role::Tiny; +1; + +use Daemon::Control; +use Role::Tiny; +use Test::More; +for ('Null', '+Daemon::Control::Plugin::Null') { + my $dc = Daemon::Control->new(plugins => $_); + $DB::single=1; + ok(Role::Tiny::does_role($dc, 'Daemon::Control::Plugin::Null'), + "Plugin role is appplied"); +} + done_testing; + From 6066671dea7cb0336ff4fe947b5da3e1e6f6ce31 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Tue, 21 Jul 2015 17:34:30 +1000 Subject: [PATCH 16/23] proof of concept hot standby plugin --- lib/Daemon/Control.pm | 11 ++--- lib/Daemon/Control/Plugin/HotStandby.pm | 55 +++++++++++++++++++++++++ t/08-null_plugin.t | 1 - t/10-hot_standby.t | 55 +++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 lib/Daemon/Control/Plugin/HotStandby.pm create mode 100644 t/10-hot_standby.t diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 10b7954..87d124f 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -514,14 +514,15 @@ sub do_show_warnings { } sub do_stop { - my ( $self ) = @_; + my ( $self, $start_pid ) = @_; - $self->read_pid; - my $start_pid = $self->pid; + if (! $start_pid) { + $self->read_pid; + $start_pid = $self->pid; + } # Probably don't want to send anything to init(1). return 1 unless $start_pid > 1; - if ( $self->pid_running($start_pid) ) { SIGNAL: foreach my $signal (@{ $self->stop_signals }) { @@ -537,7 +538,7 @@ sub do_stop { } last unless $self->pid_running($start_pid); } - if ( $self->pid_running($start_pid) ) { + if ( $ARGV[0] ne 'restart' && $self->pid_running($start_pid) ) { $self->pretty_print( "Failed to Stop", "red" ); return 1; } diff --git a/lib/Daemon/Control/Plugin/HotStandby.pm b/lib/Daemon/Control/Plugin/HotStandby.pm new file mode 100644 index 0000000..0f32b49 --- /dev/null +++ b/lib/Daemon/Control/Plugin/HotStandby.pm @@ -0,0 +1,55 @@ +package Daemon::Control::Plugin::HotStandby; +use Role::Tiny; + +=head2 NAME + +Daemon::Control::Plugin::HotStandby + +=head2 DESCRIPTION + +This is a plugin basically for PSGI workers so that a standby worker +can be spun up prior to terminating the original worker. + +Currently it doesn't work + +=head2 AUTHOR + +Kieren Diment + +=cut + + +around do_restart => sub { + my $orig = shift; + my ($self) = @_; + + # check old running + $self->read_pid; + my $old_pid = $self->pid; + if ($self->pid && $self->pid_running) { + $self->pretty_print("Found existing process"); + } + else { # warn if not + $self->pretty_print("No process running for hot standby zero downtime", "red"); + } + + + $self->_finish_start; + # Start new get pid. + $self->read_pid; + my $new_pid = $self->pid; + # check new came up. Die if failed. + sleep (($self->kill_timeout * 2) + 1); + + $self->do_stop($old_pid) if $old_pid; + $self->_ensure_pid_file_exists; + # zzz for a bit to ensure that new is actually up + # kill old + # replace old pid with new in pid file. + + + + return 0; +}; + +1; diff --git a/t/08-null_plugin.t b/t/08-null_plugin.t index 77a19de..b5c2bd7 100644 --- a/t/08-null_plugin.t +++ b/t/08-null_plugin.t @@ -10,7 +10,6 @@ use Role::Tiny; use Test::More; for ('Null', '+Daemon::Control::Plugin::Null') { my $dc = Daemon::Control->new(plugins => $_); - $DB::single=1; ok(Role::Tiny::does_role($dc, 'Daemon::Control::Plugin::Null'), "Plugin role is appplied"); } diff --git a/t/10-hot_standby.t b/t/10-hot_standby.t new file mode 100644 index 0000000..827d0c7 --- /dev/null +++ b/t/10-hot_standby.t @@ -0,0 +1,55 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More; + +my ( $file, $ilib ); + +# Let's make it so people can test in t/ or in the dist directory. +if ( -f 't/bin/10-hot_standby.pl' ) { # Dist Directory. + $file = "t/bin/10-hot_standby.pl"; + $ilib = "lib"; +} elsif ( -f 'bin/10-hot_standby.pl' ) { + $file = "bin/10-hot_standby.pl"; + $ilib = "../lib"; +} else { + die "Tests should be run in the dist directory or t/"; +} +use lib $ilib; + + +sub get_command_output { + my ( @command ) = @_; + open my $lf, "-|", @command + or die "Couldn't get pipe to '@command': $!"; + my $content = do { local $/; <$lf> }; + close $lf; + return $content; +} + +my $out; + +ok $out = get_command_output( "$^X -I$ilib $file start" ), "Started system daemon"; +like $out, qr/\[Started\]/, "Daemon started."; +ok $out = get_command_output( "$^X -I$ilib $file status" ), "Get status of system daemon."; +like $out, qr/\[Running\]/, "Daemon running."; +ok $? >> 8 == 0, "Exit Status = 0"; +sleep 10; +ok $out = get_command_output( "$^X -I$ilib $file stop" ), "Stop daemon and get status."; +ok $out = get_command_output( "$^X -I$ilib $file status" ), "Get status of system daemon."; +like $out, qr/\[Not Running\]/, "Daemon not running."; +ok $? >> 8 == 3, "Exit Status = 3"; + +# Testing restart. +ok $out = get_command_output( "$^X -I$ilib $file start" ), "Started system daemon"; +like $out, qr/\[Started\]/, "Daemon started for restarting"; +ok $out = get_command_output( "$^X -I$ilib $file status" ), "Get status of system daemon."; +like $out, qr/\[Running\]/, "Daemon running for restarting."; +ok $out = get_command_output( "$^X -I$ilib $file restart" ), "Get status of system daemon."; +like $out, qr/\[Found existing.*\[Started\]/ms, "Daemon restarted."; +ok $out = get_command_output( "$^X -I$ilib $file status" ), "Get status of system daemon."; +like $out, qr/\[Running\]/, "Daemon running after restart."; +ok $out = get_command_output( "$^X -I$ilib $file stop" ), "Get status of system daemon."; +like $out, qr/\[Stopped\]/, "Daemon stopped after restart."; + +done_testing; From 05fa858b3f69850d18f8047efd69fa028f42264d Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Wed, 22 Jul 2015 10:27:59 +1000 Subject: [PATCH 17/23] gentle refactoring of do_stop for hot standby plugin --- lib/Daemon/Control.pm | 64 +++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 87d124f..c681005 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -516,33 +516,13 @@ sub do_show_warnings { sub do_stop { my ( $self, $start_pid ) = @_; - if (! $start_pid) { - $self->read_pid; - $start_pid = $self->pid; - } + $start_pid ||= $self->_get_start_pid; # Probably don't want to send anything to init(1). return 1 unless $start_pid > 1; if ( $self->pid_running($start_pid) ) { - SIGNAL: - foreach my $signal (@{ $self->stop_signals }) { - $self->trace( "Sending $signal signal to pid $start_pid..." ); - kill $signal => $start_pid; - - for (1..$self->kill_timeout) - { - # abort early if the process is now stopped - $self->trace("checking if pid $start_pid is still running..."); - last if not $self->pid_running($start_pid); - sleep 1; - } - last unless $self->pid_running($start_pid); - } - if ( $ARGV[0] ne 'restart' && $self->pid_running($start_pid) ) { - $self->pretty_print( "Failed to Stop", "red" ); - return 1; - } - $self->pretty_print( "Stopped" ); + my $failed = $self->_send_stop_signals($start_pid); + return 1 if $failed; } else { $self->pretty_print( "Not Running", "red" ); } @@ -558,6 +538,44 @@ sub do_stop { return 0; } +sub _get_start_pid { + my ($self) = @_; + $self->read_pid; + return $self->pid; +} + +sub _send_stop_signals { + my ($self, $start_pid) = @_; + SIGNAL: + foreach my $signal (@{ $self->stop_signals }) { + $self->trace( "Sending $signal signal to pid $start_pid..." ); + kill $signal => $start_pid; + + for (1..$self->kill_timeout) + { + # abort early if the process is now stopped + $self->trace("checking if pid $start_pid is still running..."); + last if not $self->pid_running($start_pid); + sleep 1; + } + last unless $self->pid_running($start_pid); + } + if ( $ARGV[0] ne 'restart' && $self->pid_running($start_pid) ) { + $self->pretty_print( "Failed to Stop", "red" ); + return 1; + } + $self->pretty_print( "Stopped" ); +} + +sub _check_stop_outcome { + my ($self, $start_pid) = @_; +} + +sub _cleanup_pid_file { + my ($self, $start_pid) = @_; +} + + sub do_restart { my ( $self ) = @_; $self->read_pid; From 56bea13021893f49172a7d185e66ae9f63c26c11 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Wed, 22 Jul 2015 10:53:11 +1000 Subject: [PATCH 18/23] DC->with_plugins(...)->new rather than DC->new(plugins ...) --- lib/Daemon/Control.pm | 18 ++++-------------- lib/Daemon/Control/Plugin/HotStandby.pm | 15 +++++++++------ t/08-null_plugin.t | 6 +++--- t/bin/10-hot_standby.pl | 4 +--- 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index c681005..bd12884 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -86,7 +86,6 @@ sub new { my ( $class, @in ) = @_; my $args = ref $in[0] eq 'HASH' ? $in[0] : { @in }; - $class = $class->with_plugins($args->{plugins}); # Create the object with defaults. my $self = bless { @@ -567,15 +566,6 @@ sub _send_stop_signals { $self->pretty_print( "Stopped" ); } -sub _check_stop_outcome { - my ($self, $start_pid) = @_; -} - -sub _cleanup_pid_file { - my ($self, $start_pid) = @_; -} - - sub do_restart { my ( $self ) = @_; $self->read_pid; @@ -1082,20 +1072,20 @@ is tried twice). =head2 plugins -A string or an arrayref of Daemon::Control plugins to apply. Each +A string or an arrayref of Daemon::Control plugins to use. Each entry can be in the form of a fully qualified namespace prepended by a C<+>, or a string assumed to be in the C namespace. For example: - plugins => 'HotStandby' + Daemon::Control->with_plugins('HotStandby')->new(...); will load the L plugin - plugins => '+My::Plugin' + Daemon::Control->with_plugins( '+My::Plugin')->new(...); will load the C< My::Plugin > plugin - plugins => [ qw/+My::Plugin HotStandby/ ] + Daemon::Control->with_plugins(plugins => [ qw/+My::Plugin HotStandby/ ]->new(...); will load both. diff --git a/lib/Daemon/Control/Plugin/HotStandby.pm b/lib/Daemon/Control/Plugin/HotStandby.pm index 0f32b49..38170be 100644 --- a/lib/Daemon/Control/Plugin/HotStandby.pm +++ b/lib/Daemon/Control/Plugin/HotStandby.pm @@ -41,14 +41,17 @@ around do_restart => sub { # check new came up. Die if failed. sleep (($self->kill_timeout * 2) + 1); - $self->do_stop($old_pid) if $old_pid; - $self->_ensure_pid_file_exists; - # zzz for a bit to ensure that new is actually up - # kill old - # replace old pid with new in pid file. - + return 1 unless $old_pid > 1; + if ( $self->pid_running($old_pid) ) { + my $failed = $self->_send_stop_signals($old_pid); + return 1 if $failed; + } else { + $self->pretty_print( "Not Running", "red" ); + } + $self->_check_stop_outcome($old_pid); + $self->_ensure_pid_file_exists; return 0; }; diff --git a/t/08-null_plugin.t b/t/08-null_plugin.t index b5c2bd7..71b670c 100644 --- a/t/08-null_plugin.t +++ b/t/08-null_plugin.t @@ -9,9 +9,9 @@ use Daemon::Control; use Role::Tiny; use Test::More; for ('Null', '+Daemon::Control::Plugin::Null') { - my $dc = Daemon::Control->new(plugins => $_); - ok(Role::Tiny::does_role($dc, 'Daemon::Control::Plugin::Null'), + my $dc = Daemon::Control->with_plugins($_)->new(); + ok(Role::Tiny::does_role($dc, 'Daemon::Control::Plugin::Null'), "Plugin role is appplied"); } - done_testing; +done_testing; diff --git a/t/bin/10-hot_standby.pl b/t/bin/10-hot_standby.pl index 9dfde1b..c47167a 100644 --- a/t/bin/10-hot_standby.pl +++ b/t/bin/10-hot_standby.pl @@ -5,9 +5,8 @@ my ($path) = $0 =~ m{(.*/)}; my $script = $path . '10-hot_standby_daemon.sh'; -Daemon::Control->new({ +Daemon::Control->with_plugins('HotStandby')->new({ name => "My Daemon", - plugins => 'HotStandby', lsb_start => '$syslog $remote_fs', lsb_stop => '$syslog', lsb_sdesc => 'My Daemon Short', @@ -15,7 +14,6 @@ path => '/usr/sbin/mydaemon/init.pl', program => $script, - program_args => [ 10 ], pid_file => '/tmp/hotstandby_pid', From 70a8c9d31de67f06156fece0a4baa9cbf353e737 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Wed, 22 Jul 2015 11:13:02 +1000 Subject: [PATCH 19/23] slightly better test for hot standby --- t/10-hot_standby.t | 15 +++++++++++++++ t/bin/10-hot_standby.pl | 5 +---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/t/10-hot_standby.t b/t/10-hot_standby.t index 827d0c7..d6f1899 100644 --- a/t/10-hot_standby.t +++ b/t/10-hot_standby.t @@ -2,6 +2,9 @@ use warnings; use strict; use Test::More; +use File::Temp qw/tempfile/; +my ($pid_fh, $fn) = tempfile(); +$ENV{DC_TEST_TEMP_FILE} = $fn; my ( $file, $ilib ); @@ -17,6 +20,12 @@ if ( -f 't/bin/10-hot_standby.pl' ) { # Dist Directory. } use lib $ilib; +sub current_pid { + seek $pid_fh, 0, 0; + my $pid = <$pid_fh>; + chomp $pid; + return $pid; +} sub get_command_output { my ( @command ) = @_; @@ -45,9 +54,15 @@ ok $out = get_command_output( "$^X -I$ilib $file start" ), "Started system daemo like $out, qr/\[Started\]/, "Daemon started for restarting"; ok $out = get_command_output( "$^X -I$ilib $file status" ), "Get status of system daemon."; like $out, qr/\[Running\]/, "Daemon running for restarting."; +my $start_pid = current_pid(); ok $out = get_command_output( "$^X -I$ilib $file restart" ), "Get status of system daemon."; like $out, qr/\[Found existing.*\[Started\]/ms, "Daemon restarted."; +my $next_pid = current_pid(); ok $out = get_command_output( "$^X -I$ilib $file status" ), "Get status of system daemon."; + +# not sure how to check that $start_pid is still alive for a period of time before being killed at this stage. +isnt $start_pid, $next_pid, "pid file contents swapped"; + like $out, qr/\[Running\]/, "Daemon running after restart."; ok $out = get_command_output( "$^X -I$ilib $file stop" ), "Get status of system daemon."; like $out, qr/\[Stopped\]/, "Daemon stopped after restart."; diff --git a/t/bin/10-hot_standby.pl b/t/bin/10-hot_standby.pl index c47167a..4e70162 100644 --- a/t/bin/10-hot_standby.pl +++ b/t/bin/10-hot_standby.pl @@ -12,11 +12,8 @@ lsb_sdesc => 'My Daemon Short', lsb_desc => 'My Daemon controls the My Daemon daemon.', path => '/usr/sbin/mydaemon/init.pl', - program => $script, - - pid_file => '/tmp/hotstandby_pid', - + pid_file => $ENV{DC_TEST_TEMP_FILE} || '/tmp/daemon_control_manual_test_pid', stderr_file => '/tmp/test_hot_standby_err', stdout_file => '/tmp/test_hot_standby_out', From 06aae520fb44d48157916827377b10061159a7d5 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Wed, 22 Jul 2015 11:37:07 +1000 Subject: [PATCH 20/23] keep the cpan happy --- Makefile.PL | 3 ++- t/08-null_plugin.t | 13 +++++++++---- t/10-hot_standby.t | 9 +++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Makefile.PL b/Makefile.PL index 4642e6a..7d1ffd5 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -6,6 +6,7 @@ all_from 'lib/Daemon/Control.pm'; license 'perl'; # uses Module::Install::Repository +build_requires 'Module::Install::Repository' => 0; auto_set_repository; # Specific dependencies @@ -13,7 +14,7 @@ requires 'File::Spec' => '0'; requires 'POSIX' => '0'; requires 'Cwd' => '0'; requires 'File::Path' => '2.08'; +recommends 'Role::Tiny' => 0; test_requires 'Test::More' => '0.88'; - WriteAll; diff --git a/t/08-null_plugin.t b/t/08-null_plugin.t index 71b670c..337a218 100644 --- a/t/08-null_plugin.t +++ b/t/08-null_plugin.t @@ -1,17 +1,22 @@ #!/usr/bin/env perl use warnings; use strict; +BEGIN { + use Test::More; + eval 'use Role::Tiny'; + plan skip_all => 'Role::Tiny not installed' if $@; +} + package Daemon::Control::Plugin::Null; use Role::Tiny; 1; use Daemon::Control; -use Role::Tiny; -use Test::More; + for ('Null', '+Daemon::Control::Plugin::Null') { my $dc = Daemon::Control->with_plugins($_)->new(); - ok(Role::Tiny::does_role($dc, 'Daemon::Control::Plugin::Null'), + Test::More::ok(Role::Tiny::does_role($dc, 'Daemon::Control::Plugin::Null'), "Plugin role is appplied"); } -done_testing; +Test::More::done_testing; diff --git a/t/10-hot_standby.t b/t/10-hot_standby.t index d6f1899..59a3431 100644 --- a/t/10-hot_standby.t +++ b/t/10-hot_standby.t @@ -1,7 +1,12 @@ #!/usr/bin/perl use warnings; use strict; -use Test::More; +BEGIN { + use Test::More; + eval 'use Role::Tiny'; + plan skip_all => 'Role::Tiny not installed' if $@; +} + use File::Temp qw/tempfile/; my ($pid_fh, $fn) = tempfile(); $ENV{DC_TEST_TEMP_FILE} = $fn; @@ -18,7 +23,7 @@ if ( -f 't/bin/10-hot_standby.pl' ) { # Dist Directory. } else { die "Tests should be run in the dist directory or t/"; } -use lib $ilib; + sub current_pid { seek $pid_fh, 0, 0; From 154a350ca4c822b3db33be66e917eadc4c6c5218 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Wed, 22 Jul 2015 11:51:13 +1000 Subject: [PATCH 21/23] shennigans for the cpan --- Changes | 4 ++++ lib/Daemon/Control.pm | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index ef5fc39..56b2602 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,10 @@ * Module name POD format fixed (RT 93280) * Add "forground" to --help (by marcusramberg) +0.00100X 2014-02-19 Kieren Diment + * Infrastructure for daemon plugins + * HotStandby plugin for zero downtime plack/fastcgi stuff + 0.001005 2014-02-19 SymKat * Constructor now accepts a list as well as a hashref * New method added: run_command, allows multiple instances of D::C diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index bd12884..3fd6528 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -8,7 +8,7 @@ use File::Path qw( make_path ); use Cwd 'abs_path'; require 5.008001; # Supporting 5.8.1+ -our $VERSION = '0.001005'; # 0.1.5 +our $VERSION = '0.00100X'; # 0.1.X $VERSION = eval $VERSION; my @accessors = qw( From bcf9bcd9cabe0ab4f7cc2077a7f2076be225e4c5 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Wed, 22 Jul 2015 12:54:49 +1000 Subject: [PATCH 22/23] unnecessary accessor --- lib/Daemon/Control.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Daemon/Control.pm b/lib/Daemon/Control.pm index 3fd6528..bebb720 100644 --- a/lib/Daemon/Control.pm +++ b/lib/Daemon/Control.pm @@ -17,7 +17,6 @@ my @accessors = qw( lsb_start lsb_stop lsb_sdesc lsb_desc redirect_before_fork init_config kill_timeout umask resource_dir help init_code prereq_no_process foreground reload_signal stop_signals - plugins ); my $cmd_opt = "[start|stop|restart|reload|status|foreground|show_warnings|get_init_file|help]"; From 0af255dd11ad86cf4cf00c7fbf6bbe89e426f086 Mon Sep 17 00:00:00 2001 From: Kieren Diment Date: Thu, 23 Jul 2015 10:42:52 +1000 Subject: [PATCH 23/23] yes it does --- lib/Daemon/Control/Plugin/HotStandby.pm | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Daemon/Control/Plugin/HotStandby.pm b/lib/Daemon/Control/Plugin/HotStandby.pm index 38170be..27bbba2 100644 --- a/lib/Daemon/Control/Plugin/HotStandby.pm +++ b/lib/Daemon/Control/Plugin/HotStandby.pm @@ -10,8 +10,6 @@ Daemon::Control::Plugin::HotStandby This is a plugin basically for PSGI workers so that a standby worker can be spun up prior to terminating the original worker. -Currently it doesn't work - =head2 AUTHOR Kieren Diment