1package FCGI::Engine::ProcManager; 2use Moose; 3 4use constant DEBUG => 0; 5 6use POSIX qw(SA_RESTART SIGTERM SIGHUP); 7 8use FCGI::Engine::Types; 9use MooseX::Daemonize::Pid::File; 10 11our $VERSION = '0.22'; 12our $AUTHORITY = 'cpan:STEVAN'; 13 14has 'role' => ( 15 is => 'rw', 16 isa => 'FCGI::Engine::ProcManager::Role', 17 default => sub { 'manager' } 18); 19 20has 'start_delay' => ( 21 is => 'rw', 22 isa => 'Int', 23 default => sub { 0 } 24); 25 26has 'die_timeout' => ( 27 is => 'rw', 28 isa => 'Int', 29 default => sub { 60 } 30); 31 32has 'n_processes' => ( 33 is => 'rw', 34 isa => 'Int', 35 default => sub { 0 } 36); 37 38has 'pidfile' => ( 39 is => 'rw', 40 isa => 'MooseX::Daemonize::Pid::File', 41# coerce => 1, 42); 43 44has 'no_signals' => ( 45 is => 'rw', 46 isa => 'Bool', 47 default => sub { 0 } 48); 49 50has 'sigaction_no_sa_restart' => (is => 'rw', isa => 'POSIX::SigAction'); 51has 'sigaction_sa_restart' => (is => 'rw', isa => 'POSIX::SigAction'); 52 53has 'signals_received' => ( 54 is => 'rw', 55 isa => 'HashRef', 56 default => sub { +{} } 57); 58 59has 'manager_pid' => ( 60 is => 'rw', 61 isa => 'Int', 62); 63 64has '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 79sub add_pid { (shift)->_add_pid( @_, 1 ) } 80 81has 'process_name' => (is => 'ro', isa => 'Str', default => sub { 'perl-fcgi' }); 82has 'manager_process_name' => (is => 'ro', isa => 'Str', default => sub { 'perl-fcgi-pm' }); 83 84## methods ... 85 86sub 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 122sub 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 190sub 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 206sub 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 222sub 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 231sub 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 254sub 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 273sub server_sig_handler { 274 my ($self, $name) = @_; 275 $self->received_signal($name, 1); 276} 277 278sub 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 285sub change_process_name { 286 my $self = shift; 287 $0 = ($self->role eq 'manager' ? $self->manager_process_name : $self->process_name); 288} 289 290sub 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 305sub 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 321sub 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 329sub 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 361sub abort { 362 my ($self, $msg, $n) = @_; 363 $n ||= 1; 364 $self->exit($msg, 1); 365} 366 367sub 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 3811; 382 383__END__ 384 385=pod 386 387=head1 NAME 388 389FCGI::Engine::ProcManager - module for managing FastCGI applications. 390 391=head1 DESCRIPTION 392 393This module is a refactoring of L<FCGI::ProcManager>, it behaves exactly the 394same, but the API is a little different. The function-oriented API has been 395removed in favor of object-oriented API. The C<pm_> prefix has been removed 396from the hook routines and instead they now use the C<augment> and C<inner> 397functionality from L<Moose>. More docs will come eventually. 398 399=head2 Signal Handling 400 401FCGI::Engine::ProcManager attempts to do the right thing for proper shutdowns. 402 403When it receives a SIGHUP, it sends a SIGTERM to each of its children, and 404then resumes its normal operations. 405 406When it receives a SIGTERM, it sends a SIGTERM to each of its children, sets 407an alarm(3) "die timeout" handler, and waits for each of its children to 408die. If all children die before this timeout, process manager exits with 409return status 0. If all children do not die by the time the "die timeout" 410occurs, the process manager sends a SIGKILL to each of the remaining 411children, and exists with return status 1. 412 413FCGI::Engine::ProcManager uses POSIX::sigaction() to override the default 414SA_RESTART policy used for perl's %SIG behavior. Specifically, the process 415manager never uses SA_RESTART, while the child FastCGI servers turn off 416SA_RESTART around the accept loop, but re-enstate it otherwise. 417 418The desired (and implemented) effect is to give a request as big a chance as 419possible to succeed and to delay their exits until after their request, 420while allowing the FastCGI servers waiting for new requests to die right 421away. 422 423=head1 METHODS 424 425I will fill this in more eventually, but for now if you really wanna know, 426read the source. 427 428=head1 SEE ALSO 429 430=over 4 431 432=item L<FCGI::ProcManager> 433 434This module is a fork of the FCGI::ProcManager code, with lots of 435code cleanup as well as general Moosificaition. 436 437=back 438 439=head1 BUGS 440 441All complex software has bugs lurking in it, and this module is no 442exception. If you find a bug please either email me, or add the bug 443to cpan-RT. 444 445=head1 AUTHOR 446 447Stevan Little E<lt>stevan@iinteractive.comE<gt> 448 449=head1 COPYRIGHT AND LICENSE 450 451Copyright 2007-2010 by Infinity Interactive, Inc. 452 453L<http://www.iinteractive.com> 454 455This library is free software; you can redistribute it and/or modify 456it under the same terms as Perl itself. 457 458=cut 459