File Coverage

blib/lib/App/Base/Daemon/Supervisor.pm
Criterion Covered Total %
statement 48 129 37.2
branch 6 64 9.3
condition 0 6 0.0
subroutine 12 19 63.1
pod 5 5 100.0
total 71 223 31.8


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