WebReference.com - Chapter 4 from The mod_perl Developer's Cookbook, from Sams Publishing (6/6)
[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
You need to terminate an Apache child process before it reaches
MaxRequestsPerChild
.
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.
[previous] |
Copyright © Pearson Education and
Created: March 18, 2002
Revised: March 18, 2002
URL: https://webreference.com/programming/perl/cookbook/chap4/6.html