WebReference.com - Chapter 4 from The mod_perl Developer's Cookbook, from Sams Publishing (6/6) | WebReference

WebReference.com - Chapter 4 from The mod_perl Developer's Cookbook, from Sams Publishing (6/6)

To page 1To page 2To page 3To page 4To page 5current page
[previous]

mod_perl Developer's Cookbook

4.10. Terminating an Apache Child Process

You need to terminate an Apache child process before it reaches MaxRequestsPerChild.

Technique

Use the child_terminate() method from the Apache class.

eval {
 $dbh = DBI->connect($dbase, $user, $pass,
  {RaiseError => 1, AutoCommit => 1, PrintError => 1});
};
if ($@) {
 # If we could not log in, then there is the possibility under
 # Apache::DBI that the child may never recover...
 $r->server->log_error("D'oh! We may have a TNS error: $DBI::errstr ",
            "Scheduling child $$ termination NOW...");
 $r->child_terminate;
}

Comments

Part of the reason Apache is so robust (in a Unix environment at least) is due to the pre-fork model it uses to isolate each request. You probably already know that when Apache is started it begins a parent process, which then spawns a number of child processes responsible for processing the requests. The parent process will never see an actual request, but instead is responsible for managing things such as the number of child processes, signal handling, and so on. The beauty of this model is that each child is now isolated, so if a process should unexpectedly segfault, the remainder of the children remain unaffected and can continue handling requests.

There are occasions, however, when initiating a premature termination of a child process is desirable. The preceding example is derived from a specific problem that can arise when using Apache::DBI with DBD::Oracle. In some circumstances Apache::DBI becomes incapable of negotiating a new connection for a given child process, rendering that child useless for serving database-driven requests for the remainder of the child's life. To keep the child from serving any additional requests, we can call the child_terminate() method to remove the child process from the pool of available children.

When $r->child_terminate() is called, Apache will terminate the child process after it has completely finished processing all the phases of the current request. In reality, child_terminate() essentially sets MaxRequestPerChild to 1 for the current child, which may help put the notion of "terminating" the child into the proper perspective. Both Apache::SizeLimit and Apache::PerlRun successfully control child longevity using child_terminate().

As you may have suspected, because the Apache 1.3 architecture on Win32 uses a single process model, Win32 users cannot call the child_terminate() method. However, this does not mean that solving the DBD::Oracle/Apache::DBI mentioned at the start of this recipe is out of reach. Apache::DBI provides the all_handlers() class method as a way to access its internal list of cached database handles. Through some rather inelegant but effective code, we can use the hash reference returned by all_handlers() to eliminate the useless connection and force Apache::DBI to initiate a new connection for the child process.

package Cookbook::DBIUtils;
use strict;
use Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(dbh_terminate);
sub dbh_terminate {
 # Subroutine based on the connect() methods of Apache::DBI and DBI.
 # The idea is to get the id of the connection and remove it from
 # the pool of cached connections.
 # Ok, this part is stolen right from DBI->connect().
 # Remember that Apache::DBI->connect() is never called directly - it
 # receives its arguments from DBI.pm. So we have to regenerate
 # the connect string eventually received by Apache::DBI.
 my $arg = shift;
 $arg =~ s/^dbi:\w*?(?:\((.*?)\))?://i
  or '' =~ /()/;
 my $driver_attrib_spec = $1;
 unshift @_, $arg;
 
 my @args = map { defined $_ ? $_ : "" } @_;
 $driver_attrib_spec = { split /\s*=>?\s*|\s*,\s*/, $driver_attrib_spec }
  if $driver_attrib_spec;
 my $attr = {
       PrintError=>1, AutoCommit=>1,
       ref $args[3] ? %{$args[3]} : (),
       ref $driver_attrib_spec ? %$driver_attrib_spec : (),
       };
 # Now we are in Apache::DBI->connect(), where we can generate
 # the key Apache::DBI associates with the current connection.
 my $Idx = join $;, $args[0], $args[1], $args[2];
 
 while (my ($key,$val) = each %{$attr}) {
  $Idx .= "$;$key=$val";
 }
 
 # Once we have the ID of the connection, we can retrieve the
 # internal hash that Apache::DBI uses to hold the connections.
 my $handlers = Apache::DBI->all_handlers;
 
 my $r = Apache->request;
 if ($handlers->{$Idx}) {
  $r->warn("About to terminate the connection...");
  
  unless (delete $handlers->{$Idx}) {
   $r->log_error("Could not terminate connection $Idx");
   return;
  }
 }
 else {
  $r->log_error("Could not find the connection $Idx");
  return;
 }
 return 1;
}
1;

The Cookbook::DBIUtils package offers the dbh_terminate() utility function, which effectively eliminates a cached database handle from Apache::DBI's internal cache. You could use it in the same manner as our previous example.

my @args = ($dbase, $user, $pass);
my $attr = {RaiseError => 1, AutoCommit => 1, PrintError => 1};
eval {
 $dbh = DBI->connect(@args, $attr);
};
if ($@) {
 # If we could not log in, then there is the possibility under
 # Apache::DBI that the child may never recover...
 $r->server->log_error("D'oh! We may have a TNS error: $DBI::errstr ",
            "Removing connection from Apache::DBI cache...");
 Cookbook::DBIUtils::dbh_terminate(@args, $attr);
}

This approach offers not only a platform-independent solution, but may even be preferable to the child_terminate() solution—there is really no reason to terminate a child process simply because it cannot serve database-driven queries if there is another way out.


To page 1To page 2To page 3To page 4To page 5current page
[previous]

Copyright © Pearson Education and
Created: March 18, 2002
Revised: March 18, 2002


URL: http://webreference.com/programming/perl/cookbook/chap4/6.html