1 package FCGI::ProcManager;
3 # Copyright (c) 2000, FundsXpress Financial Network, Inc.
4 # This library is free software released under the GNU Lesser General
5 # Public License, Version 2.1. Please read the important licensing and
6 # disclaimer information included below.
8 # $Id: ProcManager.pm,v 1.17 2001/02/09 16:15:47 muaddie Exp $
13 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS $Q);
17 @EXPORT_OK = qw(pm_manage pm_die pm_wait
18 pm_write_pid_file pm_remove_pid_file
19 pm_pre_dispatch pm_post_dispatch
20 pm_change_process_name pm_received_signal pm_parameter
21 pm_warn pm_notify pm_abort pm_exit);
22 $EXPORT_TAGS{all} = \@EXPORT_OK;
23 $FCGI::ProcManager::Default = 'FCGI::ProcManager';
28 FCGI::ProcManager - functions for managing FastCGI applications.
33 # In Object-oriented style.
35 use FCGI::ProcManager;
36 my $proc_manager = FCGI::ProcManager->new({
39 $proc_manager->pm_manage();
40 while (my $cgi = CGI::Fast->new()) {
41 $proc_manager->pm_pre_dispatch();
42 # ... handle the request here ...
43 $proc_manager->pm_post_dispatch();
46 # This style is also supported:
48 use FCGI::ProcManager qw(pm_manage pm_pre_dispatch
50 pm_manage( n_processes => 10 );
51 while (my $cgi = CGI::Fast->new()) {
59 FCGI::ProcManager is used to serve as a FastCGI process manager. By
60 re-implementing it in perl, developers can more finely tune performance in
61 their web applications, and can take advantage of copy-on-write semantics
62 prevalent in UNIX kernel process management. The process manager should
63 be invoked before the caller''s request loop
65 The primary routine, C<pm_manage>, enters a loop in which it maintains a
66 number of FastCGI servers (via fork(2)), and which reaps those servers
67 when they die (via wait(2)).
69 C<pm_manage> provides too hooks:
71 C<managing_init> - called just before the manager enters the manager loop.
72 C<handling_init> - called just before a server is returns from C<pm_manage>
74 It is necessary for the caller, when implementing its request loop, to
75 insert a call to C<pm_pre_dispatch> at the top of the loop, and then
76 7C<pm_post_dispatch> at the end of the loop.
84 (ProcManager) new([hash parameters])
86 Constructs a new process manager. Takes an option has of initial parameter
87 values, and assigns these to the constructed object HASH, overriding any
88 default values. The default parameter values currently are:
97 my ($proto,$init) = @_;
104 $init and %$this = %$init;
106 bless $this, ref($proto)||$proto;
113 =head1 Manager methods
118 (int) pm_manage([hash parameters])
122 When this is called by a FastCGI script to manage application servers. It
123 defines a sequence of instructions for a process to enter this method and
124 begin forking off and managing those handlers, and it defines a sequence of
125 instructions to intialize those handlers.
127 If n_processes < 1, the managing section is subverted, and only the
128 handling sequence is executed.
130 Either returns the return value of pm_die() and/or pm_abort() (which will
131 not ever return in general), or returns 1 to the calling script to begin
137 my ($this,%values) = self_or_default(@_);
138 map { $this->pm_parameter($_,$values{$_}) } keys %values;
140 # skip to handling now if we won't be managing any processes.
141 $this->n_processes() or goto HANDLING;
143 # call the (possibly overloaded) management initialization hook.
144 $this->role("manager");
145 $this->managing_init();
146 $this->pm_notify("initialized");
148 my $manager_pid = $$;
150 MANAGING_LOOP: while (1) {
152 # if the calling process goes away, perform cleanup.
154 return $this->pm_die("calling process has died");
156 $this->n_processes() > 0 or
157 return $this->pm_die();
159 # while we have fewer servers than we want.
160 PIDS: while (keys(%{$this->{PIDS}}) < $this->n_processes()) {
162 if (my $pid = fork()) {
163 # the manager remembers the server.
164 $this->{PIDS}->{$pid} = { pid=>$pid };
165 $this->pm_notify("server (pid $pid) started");
167 } elsif (! defined $pid) {
168 return $this->pm_abort("fork: $!");
171 $this->role("server");
172 $this->{MANAGER_PID} = $manager_pid;
173 # the server exits the managing loop.
177 for (my $s = $this->start_delay(); $s; $s = sleep $s) {};
180 # this should block until the next server dies.
187 # forget any children we had been collecting.
188 delete $this->{PIDS};
190 # call the (possibly overloaded) handling init hook
191 $this->role("server");
192 $this->handling_init();
193 $this->pm_notify("initialized");
206 Overrideable method which initializes a process manager. In order to
207 handle signals, manage the PID file, and change the process name properly,
208 any method which overrides this should call SUPER::managing_init().
215 # begin to handle signals.
216 $SIG{TERM} = sub { $this->sig_manager(@_) };
217 $SIG{HUP} = sub { $this->sig_manager(@_) };
219 # change the name of this process as it appears in ps(1) output.
220 $this->pm_change_process_name("perl-fcgi-pm");
222 $this->pm_write_pid_file();
229 () pm_die(string msg[, int exit_status])
233 This method is called when a process manager receives a notification to
234 shut itself down. pm_die() attempts to shutdown the process manager
235 gently, sending a SIGTERM to each managed process, waiting die_timeout()
236 seconds to reap each process, and then exit gracefully once all children
237 are reaped, or to abort if all children are not reaped.
242 my ($this,$msg,$n) = self_or_default(@_);
244 # stop handling signals.
245 $SIG{HUP} = 'DEFAULT';
246 $SIG{TERM} = 'DEFAULT';
248 $this->pm_remove_pid_file();
250 # prepare to die no matter what.
251 if (defined $this->die_timeout()) {
252 $SIG{ARLM} = sub { $this->pm_abort("wait timeout") };
253 alarm $this->die_timeout();
256 # send a TERM to each of the servers.
257 kill "TERM", keys %{$this->{PIDS}};
259 # wait for the servers to die.
260 while (%{$this->{PIDS}}) {
265 $this->pm_exit("dying: ".$msg,$n);
275 This calls wait() which suspends execution until a child has exited.
276 If the process ID returned by wait corresponds to a managed process,
277 pm_notify() is called with the exit status of that process.
278 pm_wait() returns with the return value of wait().
283 my ($this) = self_or_default(@_);
285 # wait for the next server to die.
286 next if (my $pid = wait()) < 0;
288 # notify when one of our servers have died.
289 delete $this->{PIDS}->{$pid} and
290 $this->pm_notify("server (pid $pid) exited with status $?");
295 =head2 pm_write_pid_file
298 () pm_write_pid_file([string filename])
302 Writes current process ID to optionally specified file. If no filename is
303 specified, it uses the value of the C<pid_fname> parameter.
307 sub pm_write_pid_file {
308 my ($this,$fname) = self_or_default(@_);
309 $fname ||= $this->pid_fname() or return;
310 if (!open PIDFILE, ">$fname") {
311 $this->pm_warn("open: $fname: $!");
314 print PIDFILE "$$\n";
318 =head2 pm_remove_pid_file
321 () pm_remove_pid_file()
325 Removes optionally specified file. If no filename is specified, it uses
326 the value of the C<pid_fname> parameter.
330 sub pm_remove_pid_file {
331 my ($this,$fname) = self_or_default(@_);
332 $fname ||= $this->pid_fname() or return;
333 my $ret = unlink($fname) or $this->pm_warn("unlink: $fname: $!");
340 () sig_manager(string name)
344 Handles signals of the process manager. Takes as input the name of signal
350 my ($this,$name) = @_;
351 if ($name eq "TERM" or $name eq "HUP") {
352 $this->pm_notify("received signal $name");
353 $this->pm_die("safe exit from signal $name");
355 $this->pm_notify("ignoring signal $name");
359 =head1 Handler methods
373 # begin to handle signals.
374 $SIG{TERM} = sub { $this->sig_handler(@_) };
375 $SIG{HUP} = sub { $this->sig_handler(@_) };
377 # change the name of this process as it appears in ps(1) output.
378 $this->pm_change_process_name("perl-fcgi");
381 =head2 pm_pre_dispatch
390 sub pm_pre_dispatch {
391 my ($this) = self_or_default(@_);
394 =head2 pm_post_dispatch
397 () pm_post_dispatch()
403 sub pm_post_dispatch {
404 my ($this) = self_or_default(@_);
405 if ($this->pm_received_signal("TERM")) {
406 $this->pm_exit("safe exit after SIGTERM");
408 if ($this->pm_received_signal("HUP")) {
409 $this->pm_exit("safe exit after SIGHUP");
411 if ($this->{MANAGER_PID} and getppid() != $this->{MANAGER_PID}) {
412 $this->pm_exit("safe exit: manager has died");
426 my ($this,$name) = @_;
427 $this->pm_received_signal($name,1);
430 =head1 Common methods and routines
432 =head2 self_or_default
435 (ProcManager, @args) self_or_default([ ProcManager, ] @args);
439 This is a helper subroutine to acquire or otherwise create a singleton
440 default object if one is not passed in, e.g., a method call.
444 sub self_or_default {
445 return @_ if defined $_[0] and !ref $_[0] and $_[0] eq 'FCGI::ProcManager';
446 if (!defined $_[0] or (ref($_[0]) ne 'FCGI::ProcManager' and
447 !UNIVERSAL::isa($_[0],'FCGI::ProcManager'))) {
448 $Q or $Q = $FCGI::ProcManager::Default->new;
451 return wantarray ? @_ : $Q;
454 =head2 pm_change_process_name
457 () pm_change_process_name()
463 sub pm_change_process_name {
464 my ($this,$name) = self_or_default(@_);
468 =head2 pm_received_signal
471 () pm_received signal()
477 sub pm_received_signal {
478 my ($this,$sig,$received) = self_or_default(@_);
479 $sig or return $this->{SIG_RECEIVED};
480 $received and $this->{SIG_RECEIVED}->{$sig}++;
481 return $this->{SIG_RECEIVED}->{$sig};
496 my ($this,$key,$value) = self_or_default(@_);
497 defined $value and $this->{$key} = $value;
498 return $this->{$key};
517 sub n_processes { shift->pm_parameter("n_processes", @_); }
518 sub pid_fname { shift->pm_parameter("pid_fname", @_); }
519 sub no_signals { shift->pm_parameter("no_signals", @_); }
520 sub die_timeout { shift->pm_parameter("die_timeout", @_); }
521 sub role { shift->pm_parameter("role", @_); }
522 sub start_delay { shift->pm_parameter("start_delay", @_); }
524 =head1 notification and death
536 my ($this,$msg) = self_or_default(@_);
537 $this->pm_notify($msg);
550 my ($this,$msg) = self_or_default(@_);
552 print STDERR "FastCGI: ".$this->role()." (pid $$): ".$msg;
558 () pm_exit(string msg[, int exit_status])
565 my ($this,$msg,$n) = self_or_default(@_);
568 # if we still have children at this point, something went wrong.
570 kill "KILL", keys %{$this->{PIDS}} if $this->{PIDS};
572 $this->pm_warn($msg);
580 () pm_abort(string msg[, int exit_status])
587 my ($this,$msg,$n) = self_or_default(@_);
589 $this->pm_exit($msg,1);
597 No known bugs, but this does not mean no bugs exist.
605 FCGI-ProcManager - A Perl FCGI Process Manager
606 Copyright (c) 2000, FundsXpress Financial Network, Inc.
608 This library is free software; you can redistribute it and/or
609 modify it under the terms of the GNU Lesser General Public
610 License as published by the Free Software Foundation; either
611 version 2 of the License, or (at your option) any later version.
613 BECAUSE THIS LIBRARY IS LICENSED FREE OF CHARGE, THIS LIBRARY IS
614 BEING PROVIDED "AS IS WITH ALL FAULTS," WITHOUT ANY WARRANTIES
615 OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
616 LIMITATION, ANY IMPLIED WARRANTIES OF TITLE, NONINFRINGEMENT,
617 MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, AND THE
618 ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY,
619 AND EFFORT IS WITH THE YOU. See the GNU Lesser General Public
620 License for more details.
622 You should have received a copy of the GNU Lesser General Public
623 License along with this library; if not, write to the Free Software
624 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA