File Coverage

blib/lib/FCGI/Engine/ProcManager.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package FCGI::Engine::ProcManager;
2 2     2   38740 use Moose;
  0            
  0            
3              
4             use constant DEBUG => 0;
5              
6             use POSIX qw(SA_RESTART SIGTERM SIGHUP);
7              
8             use FCGI::Engine::Types;
9             use MooseX::Daemonize::Pid::File;
10              
11             our $VERSION = '0.22';
12             our $AUTHORITY = 'cpan:STEVAN';
13              
14             has 'role' => (
15             is => 'rw',
16             isa => 'FCGI::Engine::ProcManager::Role',
17             default => sub { 'manager' }
18             );
19              
20             has 'start_delay' => (
21             is => 'rw',
22             isa => 'Int',
23             default => sub { 0 }
24             );
25              
26             has 'die_timeout' => (
27             is => 'rw',
28             isa => 'Int',
29             default => sub { 60 }
30             );
31              
32             has 'n_processes' => (
33             is => 'rw',
34             isa => 'Int',
35             default => sub { 0 }
36             );
37              
38             has 'pidfile' => (
39             is => 'rw',
40             isa => 'MooseX::Daemonize::Pid::File',
41             # coerce => 1,
42             );
43              
44             has 'no_signals' => (
45             is => 'rw',
46             isa => 'Bool',
47             default => sub { 0 }
48             );
49              
50             has 'sigaction_no_sa_restart' => (is => 'rw', isa => 'POSIX::SigAction');
51             has 'sigaction_sa_restart' => (is => 'rw', isa => 'POSIX::SigAction');
52              
53             has 'signals_received' => (
54             is => 'rw',
55             isa => 'HashRef',
56             default => sub { +{} }
57             );
58              
59             has 'manager_pid' => (
60             is => 'rw',
61             isa => 'Int',
62             );
63              
64             has 'server_pids' => (
65             traits => [ 'Hash' ],
66             is => 'rw',
67             isa => 'HashRef',
68             clearer => 'forget_all_pids',
69             default => sub { +{} },
70             handles => {
71             '_add_pid' => 'set',
72             'get_all_pids' => 'keys',
73             'remove_pid' => 'delete',
74             'has_pids' => 'count',
75             'pid_count' => 'count',
76             }
77             );
78              
79             sub add_pid { (shift)->_add_pid( @_, 1 ) }
80              
81             has 'process_name' => (is => 'ro', isa => 'Str', default => sub { 'perl-fcgi' });
82             has 'manager_process_name' => (is => 'ro', isa => 'Str', default => sub { 'perl-fcgi-pm' });
83              
84             ## methods ...
85              
86             sub BUILD {
87             my $self = shift;
88             unless ($self->no_signals()) {
89             $self->sigaction_no_sa_restart(
90             POSIX::SigAction->new(
91             'FCGI::Engine::ProcManager::sig_sub'
92             )
93             );
94             $self->sigaction_sa_restart(
95             POSIX::SigAction->new(
96             'FCGI::Engine::ProcManager::sig_sub',
97             undef,
98             POSIX::SA_RESTART
99             )
100             );
101             }
102             }
103              
104             # this is the signal handler ...
105             {
106             my $SIG_CODEREF;
107              
108             sub sig_sub { $SIG_CODEREF->(@_) if ref $SIG_CODEREF }
109              
110             sub clear_signal_handler { undef $SIG_CODEREF }
111              
112             sub setup_signal_handler {
113             my $self = shift;
114             $SIG_CODEREF = $self->role eq 'manager'
115             ? sub { defined $self && $self->manager_sig_handler(@_) }
116             : sub { defined $self && $self->server_sig_handler(@_) };
117             }
118             }
119              
120             ## main loop ...
121              
122             sub manage {
123             my $self = shift;
124              
125             # skip to handling now if we won't be managing any processes.
126             $self->n_processes or return;
127              
128             # call the (possibly overloaded) management initialization hook.
129             $self->role("manager");
130             $self->manager_init;
131             $self->notify("initialized");
132              
133             my $manager_pid = $$;
134              
135             MANAGING_LOOP: while (1) {
136              
137             # FIXME
138             # we should tell the process that it is being
139             # run under some kind of daemon, which will mean
140             # that getppid will usually then return 1
141             # - SL
142             #getppid() == 1 and
143             # return $self->die("calling process has died");
144              
145             $self->n_processes > 0 or
146             return $self->die;
147              
148             # while we have fewer servers than we want.
149             PIDS: while ($self->pid_count < $self->n_processes) {
150              
151             if (my $pid = fork) {
152             # the manager remembers the server.
153             $self->add_pid($pid);
154             $self->notify("server (pid $pid) started");
155              
156             }
157             elsif (! defined $pid) {
158             return $self->abort("fork: $!");
159             }
160             else {
161             $self->manager_pid($manager_pid);
162             # the server exits the managing loop.
163             last MANAGING_LOOP;
164             }
165              
166             for (my $s = $self->start_delay; $s; $s = sleep $s) {};
167             }
168              
169             # this should block until the next server dies.
170             $self->wait;
171              
172             }# while 1
173              
174             SERVER:
175              
176             # forget any children we had been collecting.
177             $self->forget_all_pids;
178              
179             # call the (possibly overloaded) handling init hook
180             $self->role("server");
181             $self->server_init;
182             $self->notify("initialized");
183              
184             # server returns
185             return 1;
186             }
187              
188             ## initializers ...
189              
190             sub manager_init {
191             my $self = shift;
192              
193             unless ($self->no_signals) {
194             $self->setup_signal_actions(with_sa_restart => 0);
195             $self->setup_signal_handler;
196             }
197              
198             $self->change_process_name;
199              
200             eval { $self->pidfile->write };
201             $self->notify("Could not write the PID file because: $@") if $@;
202              
203             inner();
204             }
205              
206             sub server_init {
207             my $self = shift;
208              
209             unless ($self->no_signals) {
210             $self->setup_signal_actions(with_sa_restart => 0);
211             $self->setup_signal_handler;
212             }
213              
214             $self->change_process_name;
215              
216             inner();
217             }
218              
219              
220             ## hooks ...
221              
222             sub pre_dispatch {
223             my $self = shift;
224              
225             $self->setup_signal_actions(with_sa_restart => 1)
226             unless $self->no_signals;
227              
228             inner();
229             }
230              
231             sub post_dispatch {
232             my $self = shift;
233              
234             $self->exit("safe exit after SIGTERM")
235             if $self->received_signal("TERM");
236              
237             $self->exit("safe exit after SIGHUP")
238             if $self->received_signal("HUP");
239              
240             if ($self->manager_pid and getppid() != $self->manager_pid) {
241             $self->exit("safe exit: manager has died");
242             }
243              
244             $self->setup_signal_actions(with_sa_restart => 0)
245             unless $self->no_signals;
246              
247             inner();
248             }
249              
250             ## utils ...
251              
252             # sig-handlers
253              
254             sub manager_sig_handler {
255             my ($self, $name) = @_;
256             if ($name eq "TERM") {
257             $self->notify("received signal $name");
258             $self->die("safe exit from signal $name");
259             }
260             elsif ($name eq "HUP") {
261             # send a TERM to each of the servers,
262             # and pretend like nothing happened..
263             if (my @pids = $self->get_all_pids) {
264             $self->notify("sending TERM to PIDs, @pids");
265             kill TERM => @pids;
266             }
267             }
268             else {
269             $self->notify("ignoring signal $name");
270             }
271             }
272              
273             sub server_sig_handler {
274             my ($self, $name) = @_;
275             $self->received_signal($name, 1);
276             }
277              
278             sub received_signal {
279             my ($self, $sig, $received) = @_;
280             return $self->signals_received unless $sig;
281             $self->signals_received->{$sig}++ if $received;
282             return $self->signals_received->{$sig};
283             }
284              
285             sub change_process_name {
286             my $self = shift;
287             $0 = ($self->role eq 'manager' ? $self->manager_process_name : $self->process_name);
288             }
289              
290             sub wait : method {
291             my $self = shift;
292              
293             # wait for the next server to die.
294             return if (my $pid = CORE::wait()) < 0;
295              
296             # notify when one of our servers have died.
297             $self->remove_pid($pid)
298             and $self->notify("server (pid $pid) exited with status $?");
299              
300             return $pid;
301             }
302              
303             ## signal handling stuff ...
304              
305             sub setup_signal_actions {
306             my $self = shift;
307             my %args = @_;
308              
309             my $sig_action = (exists $args{with_sa_restart} && $args{with_sa_restart})
310             ? $self->sigaction_sa_restart
311             : $self->sigaction_no_sa_restart;
312              
313             POSIX::sigaction(POSIX::SIGTERM, $sig_action)
314             || $self->notify("sigaction: SIGTERM: $!");
315             POSIX::sigaction(POSIX::SIGHUP, $sig_action)
316             || $self->notify("sigaction: SIGHUP: $!");
317             }
318              
319             ## notification ...
320              
321             sub notify {
322             my ($self, $msg) = @_;
323             $msg =~ s/\s*$/\n/;
324             print STDERR "FastCGI: " . $self->role() . " (pid $$): " . $msg;
325             }
326              
327             ## error/exit handlers ...
328              
329             sub die : method {
330             my ($self, $msg, $n) = @_;
331              
332             # stop handling signals.
333             $self->clear_signal_handler;
334             $SIG{HUP} = 'DEFAULT';
335             $SIG{TERM} = 'DEFAULT';
336              
337             $self->pidfile->remove
338             || $self->notify("Could not remove PID file: $!");
339              
340             # prepare to die no matter what.
341             if (defined $self->die_timeout) {
342             $SIG{ALRM} = sub { $self->abort("wait timeout") };
343             alarm $self->die_timeout;
344             }
345              
346             # send a TERM to each of the servers.
347             if (my @pids = $self->get_all_pids) {
348             $self->notify("sending TERM to PIDs, @pids");
349             kill TERM => @pids;
350             }
351              
352             # wait for the servers to die.
353             while ($self->has_pids) {
354             $self->wait;
355             }
356              
357             # die already.
358             $self->exit("dying: $msg", $n);
359             }
360              
361             sub abort {
362             my ($self, $msg, $n) = @_;
363             $n ||= 1;
364             $self->exit($msg, 1);
365             }
366              
367             sub exit : method {
368             my ($self, $msg, $n) = @_;
369             $n ||= 0;
370              
371             # if we still have children at this point,
372             # something went wrong. SIGKILL them now.
373             kill KILL => $self->get_all_pids
374             if $self->has_pids;
375              
376             $self->notify($msg);
377             $@ = $msg;
378             CORE::exit $n;
379             }
380              
381             1;
382              
383             __END__
384              
385             =pod
386              
387             =head1 NAME
388              
389             FCGI::Engine::ProcManager - module for managing FastCGI applications.
390              
391             =head1 DESCRIPTION
392              
393             This module is a refactoring of L<FCGI::ProcManager>, it behaves exactly the
394             same, but the API is a little different. The function-oriented API has been
395             removed in favor of object-oriented API. The C<pm_> prefix has been removed
396             from the hook routines and instead they now use the C<augment> and C<inner>
397             functionality from L<Moose>. More docs will come eventually.
398              
399             =head2 Signal Handling
400              
401             FCGI::Engine::ProcManager attempts to do the right thing for proper shutdowns.
402              
403             When it receives a SIGHUP, it sends a SIGTERM to each of its children, and
404             then resumes its normal operations.
405              
406             When it receives a SIGTERM, it sends a SIGTERM to each of its children, sets
407             an alarm(3) "die timeout" handler, and waits for each of its children to
408             die. If all children die before this timeout, process manager exits with
409             return status 0. If all children do not die by the time the "die timeout"
410             occurs, the process manager sends a SIGKILL to each of the remaining
411             children, and exists with return status 1.
412              
413             FCGI::Engine::ProcManager uses POSIX::sigaction() to override the default
414             SA_RESTART policy used for perl's %SIG behavior. Specifically, the process
415             manager never uses SA_RESTART, while the child FastCGI servers turn off
416             SA_RESTART around the accept loop, but re-enstate it otherwise.
417              
418             The desired (and implemented) effect is to give a request as big a chance as
419             possible to succeed and to delay their exits until after their request,
420             while allowing the FastCGI servers waiting for new requests to die right
421             away.
422              
423             =head1 METHODS
424              
425             I will fill this in more eventually, but for now if you really wanna know,
426             read the source.
427              
428             =head1 SEE ALSO
429              
430             =over 4
431              
432             =item L<FCGI::ProcManager>
433              
434             This module is a fork of the FCGI::ProcManager code, with lots of
435             code cleanup as well as general Moosificaition.
436              
437             =back
438              
439             =head1 BUGS
440              
441             All complex software has bugs lurking in it, and this module is no
442             exception. If you find a bug please either email me, or add the bug
443             to cpan-RT.
444              
445             =head1 AUTHOR
446              
447             Stevan Little E<lt>stevan@iinteractive.comE<gt>
448              
449             =head1 COPYRIGHT AND LICENSE
450              
451             Copyright 2007-2010 by Infinity Interactive, Inc.
452              
453             L<http://www.iinteractive.com>
454              
455             This library is free software; you can redistribute it and/or modify
456             it under the same terms as Perl itself.
457              
458             =cut