File Coverage

blib/lib/App/Base/Daemon/Supervisor.pm
Criterion Covered Total %
statement 51 132 38.6
branch 6 64 9.3
condition 0 6 0.0
subroutine 13 21 61.9
pod 4 5 80.0
total 74 228 32.4


line stmt bran cond sub pod time code
1             package App::Base::Daemon::Supervisor;
2 3     3   1557 use 5.010;
  3         6  
3 3     3   1116 use Moose::Role;
  3         9372  
  3         9  
4             with 'App::Base::Daemon';
5              
6             =head1 NAME
7              
8             App::Base::Daemon::Supervisor - supervise daemon process
9              
10             =head1 SYNOPSIS
11              
12             package App::Base::Daemon::Foo;
13             use Moose;
14             with 'App::Base::Daemon::Supervisor';
15              
16             sub documentation { return 'foo'; }
17             sub options { ... }
18             sub supervised_process {
19             # the main daemon process
20             while(1) {
21             # check if supervisor is alive, exit otherwise
22             $self->ping_supervisor;
23             # do something
24             ...
25             }
26             }
27             sub supervised_shutdown {
28             # this is called during shutdown
29             }
30              
31             =head1 DESCRIPTION
32              
33             App::Base::Daemon::Supervisor allows to run code under supervision, it also
34             provides support for zero downtime reloading. When you run Supervisor based
35             daemon, the first process becomes a supervisor, it forks a child process which
36             invokes I<supervised_process> method that should contain the main daemon code.
37             Supervisor and worker connected with a socketpair. If the worker exits for some
38             reason, the supervisor detects it and starts a new worker after a small delay.
39             Worker should periodically call I<ping_supervisor> method, so it would be able
40             to detect the case when supervisor has been killed and exit.
41              
42             If module needs hot reloading feature, it should redefine I<can_do_hot_reload>
43             method to return true value. In this case supervisor process sets a handler for
44             I<SIGUSR2> signal. When I<SIGUSR2> signal is received, supervisor starts a new
45             copy of the script via fork/exec, so this new copy runs a new code. New
46             supervisor starts a worker process and waits a signal that this new worker is
47             ready to do its job. To send that signal worker should invoke
48             I<ready_to_take_over> method. Then the new supervisor receives that signal, it
49             sends I<SIGQUIT> to the old supervisor and waits for it to exit. After the old
50             supervisor exited (normally new supervisor detects that because it can flock
51             the pid file), the new supervisor sends signal to the worker,
52             I<ready_to_take_over> method in worker returns, and worker can start doing its
53             job. If supervisor receives I<SIGUSR2> when it is already in the process of
54             reloading, it ignores this signal. If supervisor didn't get I<SIGQUIT> in 60
55             seconds after starting hot reloading process, it sends I<SIGKILL> to the new
56             supervisor and resumes normal work.
57              
58             =cut
59              
60 3     3   11010 use namespace::autoclean;
  3         3  
  3         21  
61 3     3   1713 use Socket qw();
  3         7659  
  3         87  
62 3     3   15 use POSIX qw(:errno_h);
  3         6  
  3         18  
63 3     3   1170 use Time::HiRes;
  3         3  
  3         15  
64 3     3   1485 use IO::Handle;
  3         12909  
  3         111  
65 3     3   12 use Try::Tiny;
  3         3  
  3         2682  
66              
67             =head1 REQUIRED METHODS
68              
69             Class consuming this role must implement the following methods:
70              
71             =cut
72              
73             =head2 supervised_process
74              
75             The main daemon subroutine. Inside this subroutine you should periodically
76             check that supervisor is still alive using I<ping_supervisor> method. If
77             supervisor exited, daemon should also exit.
78              
79             =cut
80              
81             requires 'supervised_process';
82              
83             =head2 supervised_shutdown
84              
85             This subroutine is executed then daemon process is shutting down. Put cleanup
86             code inside.
87              
88             =cut
89              
90             requires 'supervised_shutdown';
91              
92             =head1 ATTRIBUTES
93              
94             =cut
95              
96             =head2 is_supervisor
97              
98             returns true inside supervisor process and false inside supervised daemon
99              
100             =cut
101              
102             has is_supervisor => (
103             is => 'rw',
104             default => 1,
105             );
106              
107             =head2 delay_before_respawn
108              
109             how long supervisor should wait after child process exited before starting a
110             new child. Default value is 5.
111              
112             =cut
113              
114             has delay_before_respawn => (
115             is => 'rw',
116             default => 5,
117             );
118              
119             =head2 supervisor_pipe
120              
121             File descriptor of the pipe to supervisor
122              
123             =cut
124              
125             has supervisor_pipe => (
126             is => 'rw',
127             writer => '_supervisor_pipe',
128             );
129              
130             has _child_pid => ( is => 'rw' );
131              
132             =head1 METHODS
133              
134             =cut
135              
136             =head2 $self->ping_supervisor
137              
138             Should only be called from supervised process. Checks if supervisor is alive
139             and initiates shutdown if it is not.
140              
141             =cut
142              
143             sub ping_supervisor {
144 9     9 1 902046 my $self = shift;
145 9 50       644 my $pipe = $self->supervisor_pipe or $self->error("Supervisor pipe is not defined");
146 9         273 say $pipe "ping";
147 9         623 my $pong = <$pipe>;
148 9 50       59 unless ( defined $pong ) {
149 0         0 $self->error("Error reading from supervisor pipe: $!");
150             }
151 9         56 return;
152             }
153              
154             =head2 $self->ready_to_take_over
155              
156             Used to support hot reloading. If daemon support hot restart,
157             I<supervised_process> is called while the old daemon is still running.
158             I<supervised_process> should perform initialization, e.g. open listening
159             sockets, and then call this method. Method will cause termination of old daemon
160             and after return the new process may start serving clients.
161              
162             =cut
163              
164             sub ready_to_take_over {
165 0     0 1 0 my $self = shift;
166 0 0       0 my $pipe = $self->supervisor_pipe or die "Supervisor pipe is not defined";
167 0         0 say $pipe "takeover";
168 0         0 my $ok = <$pipe>;
169 0 0       0 defined($ok) or $self->error("Failed to take over");
170 0         0 return;
171             }
172              
173             =head2 $self->daemon_run
174              
175             See L<App::Base::Daemon>
176              
177             =cut
178              
179             sub daemon_run {
180 1     1 1 5 my $self = shift;
181 1         12 $self->_set_hot_reload_handler;
182              
183 1         2 while (1) {
184 1         90 socketpair my $chld, my $par, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC;
185 1         839 my $pid = fork;
186 1         197 $self->_child_pid($pid);
187 1 50       21 if ($pid) {
    50          
188             local $SIG{QUIT} = sub {
189 0     0   0 kill TERM => $pid;
190 0         0 waitpid $pid, 0;
191 0         0 exit 0;
192 0         0 };
193 0         0 $chld->close;
194 0         0 $par->autoflush(1);
195 0         0 $self->_supervisor_pipe($par);
196 0         0 while (<$par>) {
197 0         0 chomp;
198 0 0       0 if ( $_ eq 'ping' ) {
    0          
    0          
199 0         0 say $par 'pong';
200             }
201             elsif ( $_ eq 'takeover' ) {
202 0         0 $self->_control_takeover;
203 0         0 say $par 'ok';
204             }
205             elsif ( $_ eq 'shutdown' ) {
206 0         0 kill KILL => $pid;
207 0         0 close $par;
208             }
209             else {
210 0 0       0 warn("Received unknown command from the supervised process: $_") unless $self->getOption('no-warn');
211             }
212             }
213 0         0 my $kid = waitpid $pid, 0;
214 0 0       0 warn("Supervised process $kid exited with status $?") unless $self->getOption('no-warn');
215             }
216             elsif ( not defined $pid ) {
217 0 0       0 warn("Couldn't fork: $!") unless $self->getOption('no-warn');
218             }
219             else {
220 1         34 local $SIG{USR2};
221 1         49 $par->close;
222 1         49 $chld->autoflush(1);
223 1         194 $self->_supervisor_pipe($chld);
224 1         33 $self->is_supervisor(0);
225 1         21 $self->supervised_process;
226 0         0 exit 0;
227             }
228 0         0 Time::HiRes::usleep( 1_000_000 * $self->delay_before_respawn );
229             }
230              
231             # for critic
232 0         0 return;
233             }
234              
235             # this initializes SIGUSR2 handler to perform hot reload
236             sub _set_hot_reload_handler {
237 1     1   1 my $self = shift;
238              
239 1 50       5 return unless $self->can_do_hot_reload;
240 0         0 my $upgrading;
241              
242             ## no critic (RequireLocalizedPunctuationVars)
243             $SIG{USR2} = sub {
244 0 0   0   0 return unless $ENV{APP_BASE_DAEMON_PID} == $$;
245 0 0       0 if ($upgrading) {
246 0 0       0 warn("Received USR2, but hot reload is already in progress") unless $self->getOption('no-warn');
247 0         0 return;
248             }
249 0 0       0 warn("Received USR2, initiating hot reload") unless $self->getOption('no-warn');
250 0         0 my $pid;
251 0 0       0 unless ( defined( $pid = fork ) ) {
252 0 0       0 warn("Could not fork, cancelling reload") unless $self->getOption('no-warn');
253             }
254 0 0       0 unless ($pid) {
255 0 0       0 exec( $ENV{APP_BASE_SCRIPT_EXE}, @{ $self->{orig_args} } )
  0         0  
256             or $self->error("Couldn't exec: $!");
257             }
258 0         0 $upgrading = time;
259 0 0       0 if ( $SIG{ALRM} ) {
260 0 0       0 warn("ALRM handler is already defined!") unless $self->getOption('no-warn');
261             }
262             $SIG{ALRM} = sub {
263 0 0       0 warn("Hot reloading timed out, cancelling") unless $self->getOption('no-warn');
264 0         0 kill KILL => $pid;
265 0         0 undef $upgrading;
266 0         0 };
267 0         0 alarm 60;
268 0         0 };
269             {
270 0         0 my $usr2 = POSIX::SigSet->new( POSIX::SIGUSR2() );
  0         0  
271 0         0 my $old = POSIX::SigSet->new();
272 0         0 POSIX::sigprocmask( POSIX::SIG_UNBLOCK(), $usr2, $old );
273             }
274              
275 0         0 return;
276             }
277              
278             my $pid;
279              
280             # kill the old daemon and lock pid file
281             sub _control_takeover {
282 0     0   0 my $self = shift;
283              
284             ## no critic (RequireLocalizedPunctuationVars)
285              
286             # if it is first generation, when pid file should be already locked in App::Base::Daemon
287 0 0 0     0 if ( $ENV{APP_BASE_DAEMON_GEN} > 1 and $ENV{APP_BASE_DAEMON_PID} != $$ ) {
288 0         0 kill QUIT => $ENV{APP_BASE_DAEMON_PID};
289 0 0       0 if ( $self->getOption('no-pid-file') ) {
290              
291             # we don't have pid file, so let's just poke it to death
292 0         0 my $attempts = 14;
293 0 0 0     0 while ( kill( ( $attempts == 1 ? 'KILL' : 'ZERO' ) => $ENV{APP_BASE_DAEMON_PID} )
294             and $attempts-- )
295             {
296 0         0 Time::HiRes::usleep(500_000);
297             }
298             }
299             else {
300             local $SIG{ALRM} = sub {
301 0 0   0   0 warn("Couldn't lock the file. Sending KILL to previous generation process") unless $self->getOption('no-warn');
302 0         0 };
303 0         0 alarm 5;
304              
305             # We may fail because two reasons:
306             # a) previous process didn't exit and still holds the lock
307             # b) new process was started and locked pid
308 0     0   0 $pid = try { File::Flock::Tiny->lock( $self->pid_file ) };
  0         0  
309 0 0       0 unless ($pid) {
310              
311             # So let's try killing old process, if after that locking still will fail
312             # then probably it is the case b) and we should exit
313 0         0 kill KILL => $ENV{APP_BASE_DAEMON_PID};
314 0     0   0 $SIG{ALRM} = sub { $self->error("Still couldn't lock pid file, aborting") };
  0         0  
315 0         0 alarm 5;
316 0         0 $pid = File::Flock::Tiny->lock( $self->pid_file );
317             }
318 0         0 alarm 0;
319 0         0 $pid->write_pid;
320             }
321             }
322 0         0 $ENV{APP_BASE_DAEMON_PID} = $$;
323 0         0 return;
324             }
325              
326             =head2 $self->handle_shutdown
327              
328             See L<App::Base::Daemon>
329              
330             =cut
331              
332             sub handle_shutdown {
333 1     1 1 11 my $self = shift;
334 1 50       62 if ( $self->is_supervisor ) {
335 0 0       0 kill TERM => $self->_child_pid if $self->_child_pid;
336             }
337             else {
338 1         18 $self->supervised_shutdown;
339             }
340              
341 1         312 return;
342             }
343              
344             sub DEMOLISH {
345 0     0 0   my $self = shift;
346 0 0         shutdown $self->supervisor_pipe, 2 if $self->supervisor_pipe;
347 0           return;
348             }
349              
350 3     3   15 no Moose::Role;
  3         3  
  3         18  
351             1;
352              
353             __END__
354              
355             =head1 LICENSE AND COPYRIGHT
356              
357             Copyright (C) 2010-2014 Binary.com
358              
359             This program is free software; you can redistribute it and/or modify it
360             under the terms of either: the GNU General Public License as published
361             by the Free Software Foundation; or the Artistic License.
362              
363             See http://dev.perl.org/licenses/ for more information.
364              
365             =cut