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