--- /dev/null
+2001-01-31 01:13 James Jurach <muaddib@erf.net>
+
+ * README: more documentation
+
+ * ProcManager.pm: re-organized method layout. separated manager
+ vs. server vs. common moved more intialization into managing_init()
+ and handling_init() created pm_die() to be called when manager
+ should die. cleaned up error messages and notifications.
+
+ * t/: exporter.t, procmanager.t: revised test messages
+
+2001-01-30 16:35 James Jurach <muaddib@erf.net>
+
+ * t/: exporter.t, procmanager.t: removed call to pm_state()
+
+ * ProcManager.pm: removed sleep() call after testing
+ signal-during-fork-loop
+
+2001-01-30 12:49 James Jurach <muaddib@erf.net>
+
+ * ProcManager.pm: Cleaned up pm_manage(). removed signal
+ registration routines. replaced by direct %SIG access. there is
+ now a manager signal handler and a handler signal handler. added
+ checks to die when getppid() changes.
+
+2001-01-20 02:06 James Jurach <muaddib@erf.net>
+
+ * MANIFEST: removed Changes from MANIFEST
+
+2001-01-13 00:44 James Jurach <muaddib@erf.net>
+
+ * t/procmanager.t: corrected various recent method name changes.
+ removed calls to want_to_die(). added request loop hooks to
+ pm_pre_dispatch(), pm_post_dispatch(). renamed sample_handler() to
+ sample_request_loop().
+
+ * ProcManager.pm: renamed request loop hooks to pm_pre_dispatch(),
+ pm_post_dispatch(). when pm_manage() is called with
+ n_processes==0, return through goto. renamed pre_manage_init() to
+ managing_init(). renamed post_manage_init() to handling_init().
+ added received_signal() to remember that a signal was received.
+ signal handler simpler: now only notes signal and propagates
+ signal.
+
+ * t/exporter.t: this uses non-OO calling mode.
+
+ * MANIFEST: added t/exporter.t.
+
+ * Changes: ChangeLog is where per-file changes are listed.
+
+2000-12-22 05:58 James Jurach <muaddib@erf.net>
+
+ * ProcManager.pm: corrected logic error in self_or_default().
+ changed all occurances of write_pid_file() to pm_write_pid_file().
+ changed all occurances of remove_pid_file() to
+ pm_remove_pid_file().
+
+2000-12-14 17:54 James Jurach <muaddib@erf.net>
+
+ * ProcManager.pm: corrected state() -> pm_state() method call.
+
+2000-12-10 17:25 James Jurach <muaddib@erf.net>
+
+ * ProcManager.pm: corrected some method renaming issues.
+
+2000-12-09 19:48 James Jurach <muaddib@erf.net>
+
+ * t/procmanager.t: effected the changes to these subroutine names.
+
+ * ProcManager.pm: made this module OO/Exporter hybrid. removed
+ treatment of $ENV{PROCMANAGER_PROCESSES} from constructor. changed
+ several subroutine names.
+
+2000-12-05 22:23 James Jurach <muaddib@erf.net>
+
+ * ProcManager.pm: made SIGHUP's do what SIGTERM's do
+
+2000-11-20 James Jurach <muaddib@erf.net>
+
+ * Released first public version.
# Public License, Version 2.1. Please read the important licensing and
# disclaimer information included below.
-# $Id: ProcManager.pm,v 1.12 2001/01/13 06:44:34 muaddib Exp $
+# $Id: ProcManager.pm,v 1.15 2001/01/31 07:00:55 muaddib Exp $
use strict;
use Exporter;
-use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS $Q @valid_states);
+use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS $Q);
BEGIN {
- $VERSION = '0.14';
+ $VERSION = '0.15';
@ISA = qw(Exporter);
- @EXPORT_OK = qw(pm_manage pm_parameter pm_state pm_warn pm_abort pm_exit
+ @EXPORT_OK = qw(pm_manage pm_die pm_reap_server
pm_write_pid_file pm_remove_pid_file
pm_pre_dispatch pm_post_dispatch
- pm_register_sig_handler pm_unregister_sig_handler);
+ pm_change_process_name pm_received_signal pm_parameter
+ pm_warn pm_notify pm_abort pm_exit);
$EXPORT_TAGS{all} = \@EXPORT_OK;
$FCGI::ProcManager::Default = 'FCGI::ProcManager';
-
- @valid_states = qw(managing handling idle);
}
=head1 NAME
=head1 SYNOPSIS
- # In its simplest form.
+{
+ # In Object-oriented style.
use CGI::Fast;
use FCGI::ProcManager;
- my $proc_manager = FCGI::ProcManager->new({n_processes=>10});
- $proc_manager->manage();
+ my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });
+ $proc_manager->pm_manage();
while (my $cgi = CGI::Fast->new()) {
+ $proc_manager->pm_pre_dispatch();
+ # ... handle the request here ...
+ $proc_manager->pm_post_dispatch();
+ }
+
+ # This style is also supported:
+ use CGI::Fast;
+ use FCGI::ProcManager qw(pm_manage pm_pre_dispatch pm_post_dispatch);
+ pm_manage( n_processes => 10 );
+ while (my $cgi = CGI::Fast->new()) {
+ pm_pre_dispatch();
#...
+ pm_post_dispatch();
+ }
=head1 DESCRIPTION
-FCGI::ProcManager is used to serve as a FastCGI process manager.
-The parent uses fork(2) and wait(2) to manage a set of FastCGI application
-servers. more later.
+FCGI::ProcManager is used to serve as a FastCGI process manager. By
+re-implementing it in perl, developers can more finely tune performance in
+their web applications, and can take advantage of copy-on-write semantics
+prevalent in UNIX kernel process management. The process manager should
+be invoked before the caller''s request loop
+
+The primary routine, C<pm_manage>, enters a loop in which it maintains a
+number of FastCGI servers (via fork(2)), and which reaps those servers
+when they die (via wait(2)).
+
+C<pm_manage> provides too hooks:
+
+ C<managing_init> - called just before the manager enters the manager loop.
+ C<handling_init> - called just before a server is returns from C<pm_manage>
+
+It is necessary for the caller, when implementing its request loop, to
+insert a call to C<pm_pre_dispatch> at the top of the loop, and then
+7C<pm_post_dispatch> at the end of the loop.
+
=head1 METHODS
sub new {
my ($proto,$init) = @_;
- my $this = {};
+ my $this = {
+ role => "manager",
+ start_delay => 0,
+ die_timeout => 60
+ };
$init and %$this = %$init;
bless $this, ref($proto)||$proto;
return $this;
}
-=head2 self_or_default
-
- private global
- (ProcManager, @args) self_or_default([ ProcManager, ] @args);
-
-DESCRIPTION:
-
-This is a helper subroutine to acquire or otherwise create a singleton
-default object if one is not passed in, e.g., a method call.
-
-=cut
-
-sub self_or_default {
- return @_ if defined $_[0] and !ref $_[0] and $_[0] eq 'FCGI::ProcManager';
- if (!defined $_[0] or (ref($_[0]) ne 'FCGI::ProcManager' and
- !UNIVERSAL::isa($_[0],'FCGI::ProcManager'))) {
- $Q or $Q = $FCGI::ProcManager::Default->new;
- unshift @_, $Q;
- }
- return wantarray ? @_ : $Q;
-}
+=head1 Manager methods
=head2 pm_manage
=cut
sub pm_manage {
- my ($this) = self_or_default(@_);
-
- # initialize state and begin to handle signals.
- $this->pm_register_sig_handler();
+ my ($this,%values) = self_or_default(@_);
+ map { $this->pm_parameter($_,$values{$_}) } keys %values;
- # switch to handling state right away if we are not managing any processes.
+ # skip to handling now if we won't be managing any processes.
$this->n_processes() or goto HANDLING;
- # begin the managing sequence.
- $this->pm_state("managing");
-
- # call the (possibly overloaded) managing initialization.
+ # call the (possibly overloaded) management initialization hook.
+ $this->role("manager");
$this->managing_init();
+ $this->pm_notify("initialized");
- # write out the pid file.
- $this->pm_write_pid_file();
+ my $manager_pid = $$;
- my ($pid);
- MANAGE: while (1) {
-
- # do things that we only do when we're not already managing processes..
- if (! %{$this->{PIDS}}) {
- if ($this->received_signal()) {
- $this->pm_remove_pid_file();
- $this->pm_exit("Manager $$ dying from death request.\n");
- } elsif ($this->n_processes() < 0) {
- $this->pm_remove_pid_file();
- $this->pm_abort("Manager $$ dying from processes number exception: ".
- $this->n_processes(), -( 1 + $this->n_processes()));
- }
- }
+ MANAGING_LOOP: while (1) {
+
+ # if the calling process goes away, perform cleanup.
+ getppid() == 1 and
+ return $this->pm_die("calling process has died");
- # if we have fewer children than we want..
- PIDS: while (keys(%{$this->{PIDS}}) < $this->n_processes()) {
+ $this->n_processes() > 0 or
+ return $this->pm_die();
- # fork.
- if ($pid = fork()) {
- # the parent notes the child.
- $this->pm_warn("started process $pid\n");
+ # while we have fewer servers than we want.
+ PIDS: while (keys(%{$this->{PIDS}}) < $this->n_processes()) {
+
+ if (my $pid = fork()) {
+ # the manager remembers the server.
$this->{PIDS}->{$pid} = { pid=>$pid };
+ $this->pm_notify("server (pid $pid) started");
} elsif (! defined $pid) {
- # handle errors um gracefully.
- $this->pm_abort("fork: $!\n");
+ return $this->pm_abort("fork: $!");
} else {
- # the child returns to the calling application.
- last MANAGE;
+ $this->role("server");
+ $this->{MANAGER_PID} = $manager_pid;
+ # the server exits the managing loop.
+ last MANAGING_LOOP;
}
- }
- # wait on the next child to die.
- $this->pm_abort("wait: $!\n") if ($pid = wait()) < 0;
+ for (my $s = $this->start_delay(); $s; $s = sleep $s) {};
+ }
- # notify when one of our children have died.
- delete $this->{PIDS}->{$pid} and
- $this->pm_warn("Child process $pid died with exit status $?\n");
+ # this should block until the next server dies.
+ $this->pm_reap_server();
}# while 1
- HANDLING:
- $this->pm_state("handling");
+HANDLING:
- # call the (possibly overloaded) handling initialization.
+ # call the (possibly overloaded) handling init hook
+ $this->role("server");
$this->handling_init();
+ $this->pm_notify("initialized");
- # children and parent with n_processes == 0 return to calling app.
+ # server returns
return 1;
}
sub managing_init {
my ($this) = self_or_default(@_);
-}
-=head2 handling_init
+ # begin to handle signals.
+ $SIG{TERM} = sub { $this->sig_manager(@_) };
+ $SIG{HUP} = sub { $this->sig_manager(@_) };
-=cut
+ # change the name of this process as it appears in ps(1) output.
+ $this->pm_change_process_name("perl-fcgi-pm");
-sub handling_init {
- my ($this) = self_or_default(@_);
+ $this->pm_write_pid_file();
}
-=head2 pm_pre_dispatch
+
+=head2 pm_die
=cut
-sub pm_pre_dispatch {
- my ($this) = self_or_default(@_);
+sub pm_die {
+ my ($this,$msg,$n) = self_or_default(@_);
+
+ # stop handling signals.
+ $SIG{HUP} = 'DEFAULT';
+ $SIG{TERM} = 'DEFAULT';
+
+ $this->pm_remove_pid_file();
+
+ # prepare to die no matter what.
+ if (defined $this->die_timeout()) {
+ $SIG{ARLM} = sub { $this->pm_abort("reap timeout") };
+ alarm $this->die_timeout();
+ }
+
+ # send a TERM to each of the servers.
+ kill "TERM", keys %{$this->{PIDS}};
+
+ # wait for the servers to die.
+ while (%{$this->{PIDS}}) {
+ $this->pm_reap_server();
+ }
+
+ # die already.
+ $this->pm_exit("dying: ".$msg,$n);
}
-=head2 pm_post_dispatch
+=head2 pm_reap_server
=cut
-sub pm_post_dispatch {
+sub pm_reap_server {
my ($this) = self_or_default(@_);
- if (my $name = $this->received_signal()) {
- if ($name eq "HUP" or $name eq "TERM") {
- $this->pm_exit("Process $$ responding to $name death request.\n");
- }
- }
+
+ # wait for the next server to die.
+ next if (my $pid = wait()) < 0;
+
+ # notify when one of our servers have died.
+ delete $this->{PIDS}->{$pid} and
+ $this->pm_notify("server (pid $pid) exited with status $?");
}
=head2 pm_write_pid_file
my ($this,$fname) = self_or_default(@_);
$fname ||= $this->pid_fname() or return;
if (!open PIDFILE, ">$fname") {
- $this->pm_warn("open: $fname: $!\n");
+ $this->pm_warn("open: $fname: $!");
return;
}
print PIDFILE "$$\n";
sub pm_remove_pid_file {
my ($this,$fname) = self_or_default(@_);
$fname ||= $this->pid_fname() or return;
- my $ret = unlink($fname) or $this->pm_warn("unlink: $fname: $!\n");
+ my $ret = unlink($fname) or $this->pm_warn("unlink: $fname: $!");
return $ret;
}
-=head2 pm_parameter
+=head2 sig_manager
=cut
-sub pm_parameter {
- my ($this,$key,$value) = self_or_default(@_);
- defined $value and $this->{$key} = $value;
- return $this->{$key};
+sub sig_manager {
+ my ($this,$name) = @_;
+ if ($name eq "TERM" or $name eq "HUP") {
+ $this->pm_die("received signal $name");
+ } else {
+ $this->pm_notify("ignoring signal $name");
+ }
}
-=head2 n_processes
+=head1 Handler methods
-=head2 no_signals
-
-=head2 pid_fname
+=head2 handling_init
=cut
-sub n_processes { shift->pm_parameter("n_processes", @_); }
-sub pid_fname { shift->pm_parameter("pid_fname", @_); }
-sub received_signal { shift->pm_parameter("received_signal", @_); }
-sub no_signals { shift->pm_parameter("no_signals", @_); }
-
-=head2 pm_state
+sub handling_init {
+ my ($this) = self_or_default(@_);
-=cut
+ # begin to handle signals.
+ $SIG{TERM} = sub { $this->sig_handler(@_) };
+ $SIG{HUP} = sub { $this->sig_handler(@_) };
-sub pm_state {
- my ($this,$new_state) = self_or_default(@_);
- if (defined $new_state) {
- if (!grep {$new_state eq $_} @valid_states) {
- $this->pm_abort("Invalid state: $new_state\n");
- }
- $this->{state} = $new_state;
- }
- defined $this->{state} or $this->{state} = "idle";
- return $this->{state};
+ # change the name of this process as it appears in ps(1) output.
+ $this->pm_change_process_name("perl-fcgi");
}
-=head2 pm_register_sig_handler
+=head2 pm_pre_dispatch
=cut
-sub pm_register_sig_handler {
+sub pm_pre_dispatch {
my ($this) = self_or_default(@_);
- return if $this->no_signals();
- $SIG{TERM} = sub { $this->sig_method(@_) };
- $SIG{HUP} = sub { $this->sig_method(@_) };
}
-=head2 pm_unregister_sig_handler
+=head2 pm_post_dispatch
=cut
-sub pm_unregister_sig_handler {
+sub pm_post_dispatch {
my ($this) = self_or_default(@_);
- return if $this->no_signals();
- undef $SIG{TERM};
- undef $SIG{HUP};
+ if ($this->pm_received_signal("TERM")) {
+ $this->pm_exit("safe exit after SIGTERM");
+ }
+ if ($this->pm_received_signal("HUP")) {
+ $this->pm_exit("safe exit after SIGHUP");
+ }
+ if (getppid() != $this->{MANAGER_PID}) {
+ $this->pm_exit("safe exit: manager has died");
+ }
}
-=head2 sig_method
+=head2 sig_handler
=cut
-sub sig_method {
+sub sig_handler {
my ($this,$name) = @_;
- # note which signal we've received.
- $this->{received_signal} = $name;
- $this->n_processes(0);
- # propagate this signal to children. (is this necessary?)
- if (%{$this->{PIDS}}) {
- kill $name, keys %{$this->{PIDS}};
+ $this->pm_received_signal($name,1);
+}
+
+=head1 Common methods and routines
+
+=head2 self_or_default
+
+ private global
+ (ProcManager, @args) self_or_default([ ProcManager, ] @args);
+
+DESCRIPTION:
+
+This is a helper subroutine to acquire or otherwise create a singleton
+default object if one is not passed in, e.g., a method call.
+
+=cut
+
+sub self_or_default {
+ return @_ if defined $_[0] and !ref $_[0] and $_[0] eq 'FCGI::ProcManager';
+ if (!defined $_[0] or (ref($_[0]) ne 'FCGI::ProcManager' and
+ !UNIVERSAL::isa($_[0],'FCGI::ProcManager'))) {
+ $Q or $Q = $FCGI::ProcManager::Default->new;
+ unshift @_, $Q;
}
+ return wantarray ? @_ : $Q;
+}
+
+=head2 pm_change_process_name
+
+=cut
+
+sub pm_change_process_name {
+ my ($this,$name) = self_or_default(@_);
+ $0 = $name;
+}
+
+=head2 pm_received_signal
+
+=cut
+
+sub pm_received_signal {
+ my ($this,$sig,$received) = self_or_default(@_);
+ $sig or return $this->{SIG_RECEIVED};
+ $received and $this->{SIG_RECEIVED}->{$sig}++;
+ return $this->{SIG_RECEIVED}->{$sig};
+}
+
+=head2 pm_parameter
+
+=cut
+
+sub pm_parameter {
+ my ($this,$key,$value) = self_or_default(@_);
+ defined $value and $this->{$key} = $value;
+ return $this->{$key};
}
+=head2 n_processes
+
+=head2 no_signals
+
+=head2 pid_fname
+
+=head2 die_timeout
+
+=head2 role
+
+=head2 start_delay
+
+=cut
+
+sub n_processes { shift->pm_parameter("n_processes", @_); }
+sub pid_fname { shift->pm_parameter("pid_fname", @_); }
+sub no_signals { shift->pm_parameter("no_signals", @_); }
+sub die_timeout { shift->pm_parameter("die_timeout", @_); }
+sub role { shift->pm_parameter("role", @_); }
+sub start_delay { shift->pm_parameter("start_delay", @_); }
+
=head2 pm_warn
=cut
sub pm_warn {
my ($this,$msg) = self_or_default(@_);
- print STDERR $msg;
+ $this->pm_notify($msg);
+}
+
+=head2 pm_notify
+
+=cut
+
+sub pm_notify {
+ my ($this,$msg) = self_or_default(@_);
+ $msg =~ s/\s*$/\n/;
+ print STDERR "FastCGI: ".$this->role()." (pid $$): ".$msg;
}
=head2 exit