File Coverage

blib/lib/Daemon/Control/Plugin/HotStandby.pm
Criterion Covered Total %
statement 12 38 31.5
branch 0 16 0.0
condition 0 3 0.0
subroutine 4 7 57.1
pod n/a
total 16 64 25.0


line stmt bran cond sub pod time code
1             package Daemon::Control::Plugin::HotStandby;
2             $Daemon::Control::Plugin::HotStandby::VERSION = '0.0002';
3 1     1   32017 use warnings;
  1         4  
  1         32  
4 1     1   5 use strict;
  1         2  
  1         22  
5 1     1   2040 use Role::Tiny;
  1         3824  
  1         7  
6 1     1   1087 use Class::Method::Modifiers qw/fresh/;
  1         1724  
  1         805  
7              
8             # ABSTRACT: Daemon::Control plugin to bring up new processes before disposing of the old ones.
9              
10             =head2 NAME
11              
12             Daemon::Control::Plugin::HotStandby
13              
14             =head2 DESCRIPTION
15              
16             This is a plugin basically for PSGI workers so that a standby worker
17             can be spun up prior to terminating the original worker.
18              
19             =head2 USAGE
20              
21             Daemon::Control->with_plugins('HotStandby')->new({ ... });
22              
23             =head2 NOTES and CAUTIONS
24              
25             This is not a particularly smart hot standby daemon. It uses double
26             the value of $self->kill_timeout to work out how long to wait before
27             killing the original process, after bringing its hot standby up.
28             L does something smarter, but it has the
29             disadvantage of being based on a forked, older version of
30             L, and doesn't ship with any tests. Hopefully one day
31             there will be a Daemon::Control::Plugin::HighAvailability that deals
32             with these problems, but for now this is a reasonable solution. Just
33             test it thoroughly with your kit before you send it out into the wild.
34              
35             Until we work out and optimise what needs to be factored out into
36             separate utility subroutines in L, this module contains
37             far more code than is needed (copy/paste/refactor from the parent
38             module). Also it might break depending on future releases post
39             version 0.001007 of L.
40              
41             =head2 LICENCE
42              
43             This code can be distributed under the same terms as perl itself.
44              
45             =head2 AUTHOR
46              
47             Kieren Diment
48              
49             =cut
50              
51              
52             around do_restart => sub {
53             my $orig = shift;
54             my ($self) = @_;
55              
56             # check old running
57             $self->read_pid;
58             my $old_pid = $self->pid;
59             if ($self->pid && $self->pid_running) {
60             $self->pretty_print("Found existing process");
61             }
62             else { # warn if not
63             $self->pretty_print("No process running for hot standby zero downtime", "red");
64             }
65              
66             $self->_finish_start;
67             # Start new get pid.
68             # $self->read_pid;
69              
70             # TODO check new came up. Die if failed. This is tricky. Maybe we want
71             # a custom script that works out if the $new_pid is fulfilling requests
72             # yet.
73             # my $new_pid = $self->pid;
74              
75             sleep (($self->kill_timeout * 2) + 1); # Wrong but effective
76              
77              
78             return 1 unless $old_pid > 1;
79             if ( $self->pid_running($old_pid) ) {
80             my $failed = $self->_send_stop_signals($old_pid);
81             return 1 if $failed;
82             } else {
83             $self->pretty_print( "Not Running", "red" );
84             }
85              
86             $self->_ensure_pid_file_exists;
87             return 0;
88             };
89              
90             fresh _finish_start => sub {
91 0     0     my ($self) = @_;
92 0           $self->_create_resource_dir;
93              
94 0 0         $self->fork( 2 ) unless defined $self->fork;
95 0 0         $self->_double_fork if $self->fork == 2;
96 0 0         $self->_fork if $self->fork == 1;
97 0 0         $self->_foreground if $self->fork == 0;
98 0           $self->pretty_print( "Started" );
99 0           return 0;
100             };
101              
102             fresh _send_stop_signals => sub {
103 0     0     my ($self, $start_pid) = @_;
104             SIGNAL:
105 0           foreach my $signal (@{ $self->stop_signals }) {
  0            
106 0           $self->trace( "Sending $signal signal to pid $start_pid..." );
107 0           kill $signal => $start_pid;
108            
109 0           for (1..$self->kill_timeout)
110             {
111             # abort early if the process is now stopped
112 0           $self->trace("checking if pid $start_pid is still running...");
113 0 0         last if not $self->pid_running($start_pid);
114 0           sleep 1;
115             }
116 0 0         last unless $self->pid_running($start_pid);
117             }
118 0 0 0       if ( $ARGV[0] ne 'restart' && $self->pid_running($start_pid) ) {
119 0           $self->pretty_print( "Failed to Stop", "red" );
120 0           return 1;
121             }
122 0           $self->pretty_print( "Stopped" );
123             };
124              
125             fresh _ensure_pid_file_exists => sub {
126 0     0     my ($self) = @_;
127 0 0         if ( ! -f $self->pid_file ) {
128 0           $self->pid( 0 ); # Make PID invalid.
129 0           $self->write_pid();
130             }
131             };
132              
133              
134              
135             # We need to nuke these methods and replace with our own until
136             # Daemon::Control supports what we want to do with them.
137              
138             around 'pid_running' => sub {
139             my $orig = shift;
140             my ($self, $pid) = @_;
141             $pid ||= $self->read_pid;
142              
143             return 0 unless $self->pid >= 1;
144             return 0 unless kill 0, $self->pid;
145              
146             if ( $self->scan_name ) {
147             open my $lf, "-|", "ps", "-p", $self->pid, "-o", "command="
148             or die "Failed to get pipe to ps for scan_name.";
149             while ( my $line = <$lf> ) {
150             return 1 if $line =~ $self->scan_name;
151             }
152             return 0;
153             }
154             # Scan name wasn't used, testing normal PID.
155             return kill 0, $self->pid;
156             };
157              
158              
159             1;