import FCGI-ProcManager 0.17 from CPAN
[catagits/FCGI-ProcManager.git] / ProcManager.pm
index 14ef383..7bdb537 100644 (file)
@@ -5,13 +5,24 @@ package FCGI::ProcManager;
 # Public License, Version 2.1.  Please read the important licensing and
 # disclaimer information included below.
 
-# $Id: ProcManager.pm,v 1.5 2000/11/18 07:06:09 muaddib Exp $
+# $Id: ProcManager.pm,v 1.23 2001/04/23 16:10:11 muaddie Exp $
 
 use strict;
-use vars qw(@valid_states);
+use Exporter;
+use POSIX qw(:signal_h);
+
+use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS $Q $SIG_CODEREF);
 BEGIN {
-  $FCGI::ProcManager::VERSION = '0.10';
-  @valid_states = qw(managing handling idle);
+  $VERSION = '0.17'; 
+  @ISA = qw(Exporter);
+  @EXPORT_OK = qw(pm_manage pm_die pm_wait
+                 pm_write_pid_file pm_remove_pid_file
+                 pm_pre_dispatch pm_post_dispatch
+                 pm_change_process_name pm_received_signal pm_parameter 
+                 pm_warn pm_notify pm_abort pm_exit
+                 $SIG_CODEREF);
+  $EXPORT_TAGS{all} = \@EXPORT_OK;
+  $FCGI::ProcManager::Default = 'FCGI::ProcManager';
 }
 
 =head1 NAME
@@ -20,300 +31,666 @@ BEGIN {
 
 =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.
+
+=head2 Signal Handling
+
+FCGI::ProcManager attempts to do the right thing for proper shutdowns now.
+
+When it receives a SIGHUP, it sends a SIGTERM to each of its children, and
+then resumes its normal operations.   
+
+When it receives a SIGTERM, it sends a SIGTERM to each of its children, sets
+an alarm(3) "die timeout" handler, and waits for each of its children to
+die.  If all children die before this timeout, process manager exits with
+return status 0.  If all children do not die by the time the "die timeout"
+occurs, the process manager sends a SIGKILL to each of the remaining
+children, and exists with return status 1.
+
+In order to get FastCGI servers to exit upon receiving a signal, it is
+necessary to use its FAIL_ACCEPT_ON_INTR.  See FCGI.pm's description of
+FAIL_ACCEPT_ON_INTR.  Unfortunately, if you want/need to use CGI::Fast, it
+appears currently necessary to modify your installation of FCGI.pm, with
+something like the following:
+
+ -*- patch -*-
+ --- FCGI.pm     2001/03/09 01:44:00     1.1.1.3
+ +++ FCGI.pm     2001/03/09 01:47:32     1.2
+ @@ -24,7 +24,7 @@
+  *FAIL_ACCEPT_ON_INTR = sub() { 1 };
+  
+  sub Request(;***$$$) {
+ -    my @defaults = (\*STDIN, \*STDOUT, \*STDERR, \%ENV, 0, 0);
+ +    my @defaults = (\*STDIN, \*STDOUT, \*STDERR, \%ENV, 0, FAIL_ACCEPT_ON_INTR());
+      splice @defaults,0,@_,@_;
+      RequestX(@defaults);
+  }   
+ -*- end patch -*-
+
+Otherwise, if you don't, there is a loop around accept(2) which prevents
+os_unix.c OS_Accept() from returning the necessary error when FastCGI
+servers blocking on accept(2) receive the SIGTERM or SIGHUP.
+
+FCGI::ProcManager uses POSIX::sigaction() to override the default SA_RESTART
+policy used for perl's %SIG behavior.  Specifically, the process manager
+never uses SA_RESTART, while the child FastCGI servers turn off SA_RESTART
+around the accept(2) loop, but re-enstate it otherwise.
+
+The desired (and implemented) effect is to give a request as big a chance as
+possible to succeed and to delay their exits until after their request,
+while allowing the FastCGI servers waiting for new requests to die right
+away. 
 
 =head1 METHODS
 
 =head2 new
 
+ class or instance
+ (ProcManager) new([hash parameters])
+
+Constructs a new process manager.  Takes an option has of initial parameter
+values, and assigns these to the constructed object HASH, overriding any
+default values.  The default parameter values currently are:
+
+ role         => manager
+ start_delay  => 0
+ die_timeout  => 60
+
 =cut
 
 sub new {
   my ($proto,$init) = @_;
-
-  my $this = {};
+  $init ||= {};
+
+  my $this = { 
+             role => "manager",
+             start_delay => 0,
+             die_timeout => 60,
+             %$init
+            };
   bless $this, ref($proto)||$proto;
 
-  $init and %$this = %$init;
-  defined $this->n_processes() or
-       $this->n_processes($ENV{PROCMANAGER_PROCESSES});
-
   $this->{PIDS} = {};
 
+  # initialize signal constructions.
+  unless ($this->no_signals()) {
+    $this->{sigaction_no_sa_restart} =
+       POSIX::SigAction->new('FCGI::ProcManager::sig_sub');
+    $this->{sigaction_sa_restart} =
+       POSIX::SigAction->new('FCGI::ProcManager::sig_sub',undef,POSIX::SA_RESTART);
+  }
+
   return $this;
 }
 
-=head2 manage
+=head1 Manager methods
+
+=head2 pm_manage
 
- global
- () manage(int processes_to_spawn)
+ instance or export
+ (int) pm_manage([hash parameters])
 
 DESCRIPTION:
 
-When this is called by a FastCGI script to manage application servers.
+When this is called by a FastCGI script to manage application servers.  It
+defines a sequence of instructions for a process to enter this method and
+begin forking off and managing those handlers, and it defines a sequence of
+instructions to intialize those handlers.
+
+If n_processes < 1, the managing section is subverted, and only the
+handling sequence is executed.
+
+Either returns the return value of pm_die() and/or pm_abort() (which will
+not ever return in general), or returns 1 to the calling script to begin
+handling requests.
 
 =cut
 
-sub manage {
-  my ($this) = @_;
+sub pm_manage {
+  my ($this,%values) = self_or_default(@_);
+  map { $this->pm_parameter($_,$values{$_}) } keys %values;
 
-  # initialize state and begin to handle signals.
-  $this->register_sig_handler();
-
-  # return right away if we are not managing any processes.
-  $this->n_processes() or return 1;
-
-  # call the (possibly overloaded) pre-manage initialization.
-  $this->state("managing");
-  $this->pre_manage_init();
-
-  # write out the pid file.
-  $this->write_pid_file();
-
-  my ($pid);
-  MANAGE: while (1) {
-
-    # do things that we only do when we're not already managing processes..
-    if (! %{$this->{PIDS}}) {
-      if (!$this->n_processes()) {
-       $this->remove_pid_file();
-       last;
-      } elsif ($this->want_to_die()) {
-       $this->remove_pid_file();
-       $this->exit("Manager $$ dying from death request.\n");
-      } elsif ($this->n_processes() < 0) {
-       $this->remove_pid_file();
-       $this->abort("Manager $$ dying from number of processes exception: ".
-                    $this->n_processes(), -( 1 + $this->n_processes()));
-      }
-    }
+  # skip to handling now if we won't be managing any processes.
+  $this->n_processes() or return;
+
+  # call the (possibly overloaded) management initialization hook.
+  $this->role("manager");
+  $this->managing_init();
+  $this->pm_notify("initialized");
 
-    # if we have fewer children than we want..
-    PIDS: while (keys(%{$this->{PIDS}}) < $this->n_processes()) {
+  my $manager_pid = $$;
 
-      # fork.
-      if ($pid = fork()) {
-       # the parent notes the child.
-       $this->warn("started process $pid\n");
+ MANAGING_LOOP: while (1) {
+
+    # if the calling process goes away, perform cleanup.
+    getppid() == 1 and
+      return $this->pm_die("calling process has died");
+
+    $this->n_processes() > 0 or
+      return $this->pm_die();
+
+    # 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->abort("fork: $!\n");
+       return $this->pm_abort("fork: $!");
 
       } else {
-       # the child returns to the calling application.
-       print "$$ lasting..\n";
-       last MANAGE;
+       $this->{MANAGER_PID} = $manager_pid;
+       # the server exits the managing loop.
+       last MANAGING_LOOP;
       }
+
+      for (my $s = $this->start_delay(); $s; $s = sleep $s) {};
     }
 
-    # wait on the next child to die.
-    $this->abort("wait: $!\n") if ($pid = wait()) < 0;
-    $this->warn("Child process $pid died with exit status $?\n");
-    delete $this->{PIDS}->{$pid}
-       or $this->abort("Internal error: ".
-                       "wait() returned non-existent pid=$pid??\n");
+    # this should block until the next server dies.
+    $this->pm_wait();
 
   }# while 1
 
-  # call the (possibly overloaded) post-manage initialization.
-  $this->post_manage_init();
+HANDLING:
 
-  $this->state("idle");
+  # forget any children we had been collecting.
+  delete $this->{PIDS};
 
-  print "$$ returning..\n";
-  # children and parent with n_processes == 0 return to calling app.
+  # call the (possibly overloaded) handling init hook
+  $this->role("server");
+  $this->handling_init();
+  $this->pm_notify("initialized");
+
+  # server returns 
   return 1;
 }
 
-=head2 pre_manage_init
+=head2 managing_init
 
-=cut
+ instance
+ () managing_init()
 
-sub pre_manage_init {
-  my ($this) = @_;
-}
+DESCRIPTION:
 
-=head2 post_manage_init
+Overrideable method which initializes a process manager.  In order to
+handle signals, manage the PID file, and change the process name properly,
+any method which overrides this should call SUPER::managing_init().
 
 =cut
 
-sub post_manage_init {
+sub managing_init {
   my ($this) = @_;
+
+  # begin to handle signals.
+  # We do NOT want SA_RESTART in the process manager.
+  # -- we want start the shutdown sequence immediately upon SIGTERM.
+  unless ($this->no_signals()) {
+    sigaction(SIGTERM, $this->{sigaction_no_sa_restart}) or
+       $this->pm_warn("sigaction: SIGTERM: $!");
+    sigaction(SIGHUP,  $this->{sigaction_no_sa_restart}) or
+       $this->pm_warn("sigaction: SIGHUP: $!");
+    $SIG_CODEREF = sub { $this->sig_manager(@_) };
+  }
+
+  # change the name of this process as it appears in ps(1) output.
+  $this->pm_change_process_name("perl-fcgi-pm");
+
+  $this->pm_write_pid_file();
 }
 
-=head2 pre_dispatch
+=head2 pm_die
+
+ instance or export
+ () pm_die(string msg[, int exit_status])
+
+DESCRIPTION:
+
+This method is called when a process manager receives a notification to
+shut itself down.  pm_die() attempts to shutdown the process manager
+gently, sending a SIGTERM to each managed process, waiting die_timeout()
+seconds to reap each process, and then exit gracefully once all children
+are reaped, or to abort if all children are not reaped.
 
 =cut
 
-sub pre_dispatch {
-  my ($this) = @_;
-  $this->state("handling");
+sub pm_die {
+  my ($this,$msg,$n) = self_or_default(@_);
+
+  # stop handling signals.
+  undef $SIG_CODEREF;
+  $SIG{HUP}  = 'DEFAULT';
+  $SIG{TERM} = 'DEFAULT';
+
+  $this->pm_remove_pid_file();
+
+  # prepare to die no matter what.
+  if (defined $this->die_timeout()) {
+    $SIG{ALRM} = sub { $this->pm_abort("wait timeout") };
+    alarm $this->die_timeout();
+  }
+
+  # send a TERM to each of the servers.
+  if (my @pids = keys %{$this->{PIDS}}) {
+    $this->pm_notify("sending TERM to PIDs, @pids");
+    kill "TERM", @pids;
+  }
+
+  # wait for the servers to die.
+  while (%{$this->{PIDS}}) {
+    $this->pm_wait();
+  }
+
+  # die already.
+  $this->pm_exit("dying: ".$msg,$n);
 }
 
-=head2 post_dispatch
+=head2 pm_wait
+
+ instance or export
+ (int pid) pm_wait()
+
+DESCRIPTION:
+
+This calls wait() which suspends execution until a child has exited.
+If the process ID returned by wait corresponds to a managed process,
+pm_notify() is called with the exit status of that process.
+pm_wait() returns with the return value of wait().
 
 =cut
 
-sub post_dispatch {
-  my ($this) = @_;
-  $this->want_to_die() and 
-    $this->exit("Process $$ responding to death request.");
-  $this->state("idle");
+sub pm_wait {
+  my ($this) = self_or_default(@_);
+
+  # 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 $?");
+
+  return $pid;
 }
 
-=head2 write_pid_file
+=head2 pm_write_pid_file
+
+ instance or export
+ () pm_write_pid_file([string filename])
+
+DESCRIPTION:
+
+Writes current process ID to optionally specified file.  If no filename is
+specified, it uses the value of the C<pid_fname> parameter.
 
 =cut
 
-sub write_pid_file {
-  my ($this,$fname) = @_;
+sub pm_write_pid_file {
+  my ($this,$fname) = self_or_default(@_);
   $fname ||= $this->pid_fname() or return;
   if (!open PIDFILE, ">$fname") {
-    $this->warn("open: $fname: $!\n");
+    $this->pm_warn("open: $fname: $!");
     return;
   }
   print PIDFILE "$$\n";
   close PIDFILE;
 }
 
-=head2 remove_pid_file
+=head2 pm_remove_pid_file
+
+ instance or export
+ () pm_remove_pid_file()
+
+DESCRIPTION:
+
+Removes optionally specified file.  If no filename is specified, it uses
+the value of the C<pid_fname> parameter.
 
 =cut
 
-sub remove_pid_file {
-  my ($this,$fname) = @_;
+sub pm_remove_pid_file {
+  my ($this,$fname) = self_or_default(@_);
   $fname ||= $this->pid_fname() or return;
-  my $ret = unlink($fname) or $this->warn("unlink: $fname: $!\n");
+  my $ret = unlink($fname) or $this->pm_warn("unlink: $fname: $!");
   return $ret;
 }
 
-=head2 gen_mutator
+=head2 sig_sub
+
+ instance
+ () sig_sub(string name)
+
+DESCRIPTION:
+
+The name of this method is passed to POSIX::sigaction(), and handles signals
+for the process manager.  If $SIG_CODEREF is set, then the input arguments
+to this are passed to a call to that.
 
 =cut
 
-sub gen_mutator {
-  my ($this,$key,$value) = @_;
-  defined $value and $this->{$key} = $value;
-  return $this->{$key};
+sub sig_sub {
+  $SIG_CODEREF->(@_) if ref $SIG_CODEREF;
 }
 
-=head2 n_processes
+=head2 sig_manager
 
-=head2 want_to_die
+ instance
+ () sig_manager(string name)
 
-=head2 no_signals
+DESCRIPTION:
 
-=head2 pid_fname
+Handles signals of the process manager.  Takes as input the name of signal
+being handled.
 
 =cut
 
-sub n_processes { shift->gen_mutator("n_processes",@_); }
-sub want_to_die { shift->gen_mutator("want_to_die",@_); }
-sub no_signals  { shift->gen_mutator("no_signals",@_);  }
-sub pid_fname   { shift->gen_mutator("pid_fname",@_);   }
+sub sig_manager {
+  my ($this,$name) = @_;
+  if ($name eq "TERM") {
+    $this->pm_notify("received signal $name");
+    $this->pm_die("safe exit from signal $name");
+  } elsif ($name eq "HUP") {
+    # send a TERM to each of the servers, and pretend like nothing happened..
+    if (my @pids = keys %{$this->{PIDS}}) {
+      $this->pm_notify("sending TERM to PIDs, @pids");
+      kill "TERM", @pids;
+    }
+  } else {
+    $this->pm_notify("ignoring signal $name");
+  }
+}
+
+=head1 Handler methods
+
+=head2 handling_init
+
+ instance or export
+ () handling_init()
 
-=head2 state
+DESCRIPTION:
 
 =cut
 
-sub state {
-  my ($this,$new_state) = @_;
-  if (defined $new_state) {
-    if (!grep {$new_state eq $_} @valid_states) {
-      $this->abort("Invalid state: $new_state\n");
-    }
-    $this->{state} = $new_state;
+sub handling_init {
+  my ($this) = @_;
+
+  # begin to handle signals.
+  # We'll want accept(2) to return -1(EINTR) on caught signal..
+  unless ($this->no_signals()) {
+    sigaction(SIGTERM, $this->{sigaction_no_sa_restart}) or $this->pm_warn("sigaction: SIGTERM: $!");
+    sigaction(SIGHUP,  $this->{sigaction_no_sa_restart}) or $this->pm_warn("sigaction: SIGHUP: $!");
+    $SIG_CODEREF = sub { $this->sig_handler(@_) };
   }
-  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 register_sig_handler
+=head2 pm_pre_dispatch
+
+ instance or export
+ () pm_pre_dispatch()
+
+DESCRIPTION:
 
 =cut
 
-sub register_sig_handler {
-  my ($this) = @_;
-  return if $this->no_signals();
-  $SIG{TERM} = sub { $this->sig_method(@_) };
-  $SIG{HUP} = sub { $this->sig_method(@_) };
+sub pm_pre_dispatch {
+  my ($this) = self_or_default(@_);
+
+  # Now, we want the request to continue unhindered..
+  unless ($this->no_signals()) {
+    sigaction(SIGTERM, $this->{sigaction_sa_restart}) or $this->pm_warn("sigaction: SIGTERM: $!");
+    sigaction(SIGHUP,  $this->{sigaction_sa_restart}) or $this->pm_warn("sigaction: SIGHUP: $!");
+  }
 }
 
-=head2 unregister_sig_handler
+=head2 pm_post_dispatch
+
+ instance or export
+ () pm_post_dispatch()
+
+DESCRIPTION:
 
 =cut
 
-sub unregister_sig_handler {
-  my ($this) = @_;
-  return if $this->no_signals();
-  undef $SIG{TERM};
-  undef $SIG{HUP};
+sub pm_post_dispatch {
+  my ($this) = self_or_default(@_);
+  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 ($this->{MANAGER_PID} and getppid() != $this->{MANAGER_PID}) {
+    $this->pm_exit("safe exit: manager has died");
+  }
+  # We'll want accept(2) to return -1(EINTR) on caught signal..
+  unless ($this->no_signals()) {
+    sigaction(SIGTERM, $this->{sigaction_no_sa_restart}) or $this->pm_warn("sigaction: SIGTERM: $!");
+    sigaction(SIGHUP,  $this->{sigaction_no_sa_restart}) or $this->pm_warn("sigaction: SIGHUP: $!");
+  }
 }
 
-=head2 sig_method
+=head2 sig_handler
+
+ instance or export
+ () sig_handler()
+
+DESCRIPTION:
 
 =cut
 
-sub sig_method {
+sub sig_handler {
   my ($this,$name) = @_;
-  if ($name eq "TERM") {
-    if ($this->state() eq "idle") {
-      $this->exit("Process $$ dying after receiving SIG$name.\n");
-    } else {
-      $this->warn("Process $$ received SIG$name.  Cleaning up.\n");
-      $this->want_to_die(1);
-      $this->n_processes(-1);
-      # is the following necessary?
-      kill $name, keys %{$this->{PIDS}};
-    }
-  } else {
-    $this->warn("I don't know what to do with $name yet.. ignoring?\n");
+  $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
+
+ instance or export
+ () pm_change_process_name()
+
+DESCRIPTION:
+
+=cut
+
+sub pm_change_process_name {
+  my ($this,$name) = self_or_default(@_);
+  $0 = $name;
+}
+
+=head2 pm_received_signal
+
+ instance or export
+ () pm_received signal()
+
+DESCRIPTION:
+
+=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};
+}
+
+=head1 parameters
+
+=head2 pm_parameter
+
+ instance or export
+ () pm_parameter()
+
+DESCRIPTION:
+
+=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
+
+DESCRIPTION:
+
+=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",     @_); }
+
+=head1 notification and death
+
+=head2 pm_warn
+
+ instance or export
+ () pm_warn()
+
+DESCRIPTION:
+
+=cut
+
+sub pm_warn {
+  my ($this,$msg) = self_or_default(@_);
+  $this->pm_notify($msg);
 }
 
-=head2 warn
+=head2 pm_notify
+
+ instance or export
+ () pm_notify()
+
+DESCRIPTION:
 
 =cut
 
-sub warn {
-  my ($this,$msg) = @_;
-  print STDERR $msg;
+sub pm_notify {
+  my ($this,$msg) = self_or_default(@_);
+  $msg =~ s/\s*$/\n/;
+  print STDERR "FastCGI: ".$this->role()." (pid $$): ".$msg;
 }
 
-=head2 exit
+=head2 pm_exit
+
+ instance or export
+ () pm_exit(string msg[, int exit_status])
+
+DESCRIPTION:
 
 =cut
 
-sub exit {
-  my ($this,$msg,$n) = @_;
+sub pm_exit {
+  my ($this,$msg,$n) = self_or_default(@_);
   $n ||= 0;
-  $this->warn($msg);
+
+  # if we still have children at this point, something went wrong.
+  # SIGKILL them now.
+  kill "KILL", keys %{$this->{PIDS}} if $this->{PIDS};
+
+  $this->pm_warn($msg);
   $@ = $msg;
   exit $n;
 }
 
-=head2 abort
+=head2 pm_abort
+
+ instance or export
+ () pm_abort(string msg[, int exit_status])
+
+DESCRIPTION:
 
 =cut
 
-sub abort {
-  my ($this,$msg,$n) = @_;
+sub pm_abort {
+  my ($this,$msg,$n) = self_or_default(@_);
   $n ||= 1;
-  $this->exit($msg,1);
+  $this->pm_exit($msg,1);
 }
 
 1;