You are viewing a plain text version of this content. The canonical link for it is here.
Posted to modperl-cvs@perl.apache.org by pg...@apache.org on 2007/03/26 16:54:41 UTC
svn commit: r522534 - /perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm
Author: pgollucci
Date: Mon Mar 26 07:54:40 2007
New Revision: 522534
URL: http://svn.apache.org/viewvc?view=rev&rev=522534
Log:
copied unchanged from https://svn.apache.org/repos/asf/perl/Apache-SizeLimit/tags/0_9/lib/Apache/SizeLimit.pm
Modified:
perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm
Modified: perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm
URL: http://svn.apache.org/viewvc/perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm?view=diff&rev=522534&r1=522533&r2=522534
==============================================================================
--- perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm (original)
+++ perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm Mon Mar 26 07:54:40 2007
@@ -1,338 +1,688 @@
+# Copyright 2001-2006 The Apache Software Foundation or its licensors, as
+# applicable.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
package Apache::SizeLimit;
-=head1 NAME
+use Apache::Constants qw(DECLINED OK);
+use Config;
+use strict;
+use vars qw(
+ $VERSION
+ $REQUEST_COUNT
+ $START_TIME
+ $USE_SMAPS
+);
-Apache::SizeLimit - Because size does matter.
+$VERSION = '0.9';
-=head1 SYNOPSIS
+__PACKAGE__->set_check_interval(1);
-This module allows you to kill off Apache httpd processes if they grow too
-large. You can choose to set up the process size limiter to check the
-process size on every request:
-
- # in your startup.pl:
- use Apache::SizeLimit;
- # sizes are in KB
- $Apache::SizeLimit::MAX_PROCESS_SIZE = 10000; # 10MB
- $Apache::SizeLimit::MIN_SHARE_SIZE = 1000; # 1MB
- $Apache::SizeLimit::MAX_UNSHARED_SIZE = 12000; # 12MB
+$REQUEST_COUNT = 1;
+$USE_SMAPS = 1;
- # in your httpd.conf:
- PerlCleanupHandler Apache::SizeLimit
+use constant IS_WIN32 => $Config{'osname'} eq 'MSWin32' ? 1 : 0;
-Or you can just check those requests that are likely to get big, such as
-CGI requests. This way of checking is also easier for those who are mostly
-just running CGI.pm/Registry scripts:
-
- # in your CGI:
- use Apache::SizeLimit;
- &Apache::SizeLimit::setmax(10000); # Max size in KB
- &Apache::SizeLimit::setmin(1000); # Min share in KB
- &Apache::SizeLimit::setmax_unshared(12000); # Max unshared size in KB
-Since checking the process size can take a few system calls on some
-platforms (e.g. linux), you may want to only check the process size every
-N times. To do so, put this in your startup.pl or CGI:
+use vars qw( $MAX_PROCESS_SIZE );
+sub set_max_process_size {
+ my $class = shift;
- $Apache::SizeLimit::CHECK_EVERY_N_REQUESTS = 2;
+ $MAX_PROCESS_SIZE = shift;
+}
-This will only check the process size every other time the process size
-checker is called.
+use vars qw( $MAX_UNSHARED_SIZE );
+sub set_max_unshared_size {
+ my $class = shift;
-=head1 DESCRIPTION
+ $MAX_UNSHARED_SIZE = shift;
+}
-This module is highly platform dependent, please read the CAVEATS section.
+use vars qw( $MIN_SHARE_SIZE );
+sub set_min_shared_size {
+ my $class = shift;
-This module was written in response to questions on the mod_perl mailing
-list on how to tell the httpd process to exit if it gets too big.
+ $MIN_SHARE_SIZE = shift;
+}
-Actually there are two big reasons your httpd children will grow. First,
-it could have a bug that causes the process to increase in size
-dramatically, until your system starts swapping. Second, your process just
-does stuff that requires a lot of memory, and the more different kinds of
-requests your server handles, the larger the httpd processes grow over
-time.
-
-This module will not really help you with the first problem. For that you
-should probably look into Apache::Resource or some other means of setting a
-limit on the data size of your program. BSD-ish systems have setrlimit()
-which will croak your memory gobbling processes. However it is a little
-violent, terminating your process in mid-request.
-
-This module attempts to solve the second situation where your process
-slowly grows over time. The idea is to check the memory usage after every
-request, and if it exceeds a threshold, exit gracefully.
-
-By using this module, you should be able to discontinue using the Apache
-configuration directive B<MaxRequestsPerChild>, although for some folks,
-using both in combination does the job. Personally, I just use the
-technique shown in this module and set my MaxRequestsPerChild value to
-6000.
+use vars qw( $CHECK_EVERY_N_REQUESTS );
+sub set_check_interval {
+ my $class = shift;
-=head1 SHARED MEMORY OPTIONS
+ $CHECK_EVERY_N_REQUESTS = shift;
+}
-In addition to simply checking the total size of a process, this
-module can factor in how much of the memory used by the process is
-actually being shared by copy-on-write. If you don't understand how
-memory is shared in this way, take a look at the mod_perl Guide at
-http://perl.apache.org/guide/.
+sub handler ($$) {
+ my $class = shift;
+ my $r = shift || Apache->request;
-You can take advantage of the shared memory information by setting a
-minimum shared size and/or a maximum unshared size. Experience on one
-heavily trafficked mod_perl site showed that setting maximum unshared
-size and leaving the others unset is the most effective policy. This
-is because it only kills off processes that are truly using too much
-physical RAM, allowing most processes to live longer and reducing the
-process churn rate.
+ return DECLINED unless $r->is_main();
-=head1 CAVEATS
+ # we want to operate in a cleanup handler
+ if ( $r->current_callback eq 'PerlCleanupHandler' ) {
+ return $class->_exit_if_too_big($r);
+ }
+ else {
+ $class->add_cleanup_handler($r);
+ }
-This module is platform dependent, since finding the size of a process
-is pretty different from OS to OS, and some platforms may not be
-supported. In particular, the limits on minimum shared memory and
-maximum shared memory are currently only supported on Linux and BSD.
-If you can contribute support for another OS, please do.
+ return DECLINED;
+}
-Currently supported OSes:
+sub add_cleanup_handler {
+ my $class = shift;
+ my $r = shift || Apache->request;
-=over 4
+ return unless $r;
+ return if $r->pnotes('size_limit_cleanup');
-=item linux
+ # This used to use $r->post_connection but there's no good way to
+ # test it, since apparently it does not push a handler onto the
+ # PerlCleanupHandler phase. That means that there's no way to use
+ # $r->get_handlers() to check the results of calling this method.
+ $r->push_handlers( 'PerlCleanupHandler',
+ sub { $class->_exit_if_too_big() } );
+ $r->pnotes( size_limit_cleanup => 1 );
+}
-For linux we read the process size out of /proc/self/status. This is
-a little slow, but usually not too bad. If you are worried about
-performance, try only setting up the the exit handler inside CGIs
-(with the C<setmax> function), and see if the CHECK_EVERY_N_REQUESTS
-option is of benefit.
+sub _exit_if_too_big {
+ my $class = shift;
+ my $r = shift;
-=item solaris 2.6 and above
+ return DECLINED
+ if ( $CHECK_EVERY_N_REQUESTS
+ && ( $REQUEST_COUNT++ % $CHECK_EVERY_N_REQUESTS ) );
-For solaris we simply retrieve the size of /proc/self/as, which
-contains the address-space image of the process, and convert to KB.
-Shared memory calculations are not supported.
+ $START_TIME ||= time;
-NOTE: This is only known to work for solaris 2.6 and above. Evidently
-the /proc filesystem has changed between 2.5.1 and 2.6. Can anyone
-confirm or deny?
+ if ( $class->_limits_are_exceeded() ) {
+ my ( $size, $share, $unshared ) = $class->_check_size();
+
+ if ( IS_WIN32 || $class->_platform_getppid() > 1 ) {
+ # this is a child httpd
+ my $e = time - $START_TIME;
+ my $msg = "httpd process too big, exiting at SIZE=$size KB";
+ $msg .= " SHARE=$share KB UNSHARED=$unshared" if ($share);
+ $msg .= " REQUESTS=$REQUEST_COUNT LIFETIME=$e seconds";
+ $class->_error_log($msg);
+
+ if (IS_WIN32) {
+ # child_terminate() is disabled in win32 Apache
+ CORE::exit(-2);
+ }
+ else {
+ $r->child_terminate();
+ }
+ }
+ else {
+ # this is the main httpd, whose parent is init?
+ my $msg = "main process too big, SIZE=$size KB ";
+ $msg .= " SHARE=$share KB" if ($share);
+ $class->_error_log($msg);
+ }
+ }
+ return OK;
+}
-=item *bsd*
+# REVIEW - Why doesn't this use $r->warn or some other
+# Apache/Apache::Log API?
+sub _error_log {
+ my $class = shift;
-Uses BSD::Resource::getrusage() to determine process size. This is pretty
-efficient (a lot more efficient than reading it from the /proc fs anyway).
+ print STDERR "[", scalar( localtime(time) ),
+ "] ($$) Apache::SizeLimit @_\n";
+}
-=item AIX?
+sub _limits_are_exceeded {
+ my $class = shift;
-Uses BSD::Resource::getrusage() to determine process size. Not sure if the
-shared memory calculations will work or not. AIX users?
+ my ( $size, $share, $unshared ) = $class->_check_size();
-=item Win32
+ return 1 if $MAX_PROCESS_SIZE && $size > $MAX_PROCESS_SIZE;
-Uses Win32::API to access process memory information. Win32::API can be
-installed under ActiveState perl using the supplied ppm utility.
+ return 0 unless $share;
-=back
+ return 1 if $MIN_SHARE_SIZE && $share < $MIN_SHARE_SIZE;
-If your platform is not supported, and if you can tell me how to check for
-the size of a process under your OS (in KB), then I will add it to the list.
-The more portable/efficient the solution, the better, of course.
+ return 1 if $MAX_UNSHARED_SIZE && $unshared > $MAX_UNSHARED_SIZE;
-=head1 TODO
+ return 0;
+}
-Possibly provide a perl make/install so that the SizeLimit.pm is created at
-build time with only the code you need on your platform.
+sub _check_size {
+ my ( $size, $share ) = _platform_check_size();
-If Apache was started in non-forking mode, should hitting the size limit
-cause the process to exit?
+ return ( $size, $share, $size - $share );
+}
-=cut
+sub _load {
+ my $mod = shift;
-use Apache::Constants qw(:common);
-use Config;
-use strict;
-use vars qw($VERSION $HOW_BIG_IS_IT $MAX_PROCESS_SIZE
- $REQUEST_COUNT $CHECK_EVERY_N_REQUESTS
- $MIN_SHARE_SIZE $MAX_UNSHARED_SIZE $START_TIME $WIN32);
-
-$VERSION = '0.03';
-$CHECK_EVERY_N_REQUESTS = 1;
-$REQUEST_COUNT = 1;
-$MAX_PROCESS_SIZE = 0;
-$MIN_SHARE_SIZE = 0;
-$MAX_UNSHARED_SIZE = 0;
+ eval "require $mod"
+ or die "You must install $mod for Apache::SizeLimit to work on your platform.";
+}
BEGIN {
- $WIN32 = 0;
- # decide at compile time how to check for a process' memory size.
- if (($Config{'osname'} eq 'solaris') &&
- ($Config{'osvers'} >= 2.6)) {
- $HOW_BIG_IS_IT = \&solaris_2_6_size_check;
- } elsif ($Config{'osname'} eq 'linux') {
- $HOW_BIG_IS_IT = \&linux_size_check;
- } elsif ($Config{'osname'} =~ /(bsd|aix|darwin)/i) {
- # will getrusage work on all BSDs? I should hope so.
- if (eval("require BSD::Resource;")) {
- $HOW_BIG_IS_IT = \&bsd_size_check;
- } else {
- die "you must install BSD::Resource for Apache::SizeLimit to work on your platform.";
- }
- } elsif ($Config{'osname'} eq 'MSWin32') {
- $WIN32 = 1;
- if (eval("require Win32::API")) {
- $HOW_BIG_IS_IT = \&win32_size_check;
- } else {
- die "you must install Win32::API for Apache::SizeLimit to work on your platform.";
+ if ( $Config{'osname'} eq 'solaris'
+ && $Config{'osvers'} >= 2.6 ) {
+ *_platform_check_size = \&_solaris_2_6_size_check;
+ *_platform_getppid = \&_perl_getppid;
+ }
+ elsif ( $Config{'osname'} eq 'linux' ) {
+ _load('Linux::Pid');
+
+ *_platform_getppid = \&_linux_getppid;
+
+ if ( eval { require Linux::Smaps } && Linux::Smaps->new($$) ) {
+ *_platform_check_size = \&_linux_smaps_size_check;
+ }
+ else {
+ $USE_SMAPS = 0;
+ *_platform_check_size = \&_linux_size_check;
}
- } else {
- die "Apache::SizeLimit not implemented on your platform.";
}
+ elsif ( $Config{'osname'} =~ /(?:bsd|aix)/i ) {
+ # on OSX, getrusage() is returning 0 for proc & shared size.
+ _load('BSD::Resource');
+
+ *_platform_check_size = \&_bsd_size_check;
+ *_platform_getppid = \&_perl_getppid;
+ }
+ elsif (IS_WIN32) {
+ _load('Win32::API');
+
+ *_platform_check_size = \&_win32_size_check;
+ *_platform_getppid = \&_perl_getppid;
+ }
+ else {
+ die "Apache::SizeLimit is not implemented on your platform.";
+ }
+}
+
+sub _linux_smaps_size_check {
+ my $class = shift;
+
+ return $class->_linux_size_check() unless $USE_SMAPS;
+
+ my $s = Linux::Smaps->new($$)->all;
+ return ($s->size, $s->shared_clean + $s->shared_dirty);
}
-# return process size (in KB)
-sub linux_size_check {
- my ($size, $resident, $share) = (0,0,0);
- local(*FH);
- if (open(FH, "</proc/self/statm")) {
- ($size, $resident, $share) = split(/\s/, scalar <FH>);
- close(FH);
- } else {
- &error_log("Fatal Error: couldn't access /proc/self/status");
+sub _linux_size_check {
+ my $class = shift;
+
+ my ( $size, $share ) = ( 0, 0 );
+
+ if ( open my $fh, '<', '/proc/self/statm' ) {
+ ( $size, $share ) = ( split /\s/, scalar <$fh> )[0,2];
+ close $fh;
}
+ else {
+ $class->_error_log("Fatal Error: couldn't access /proc/self/status");
+ }
+
# linux on intel x86 has 4KB page size...
- return($size*4, $share*4);
+ return ( $size * 4, $share * 4 );
}
-sub solaris_2_6_size_check {
- my $size = -s "/proc/self/as" or
- &error_log("Fatal Error: /proc/self/as doesn't exist or is empty");
- $size = int($size/1024); # to get it into kb
- return($size, 0); # return 0 for share, to avoid undef warnings
+sub _solaris_2_6_size_check {
+ my $class = shift;
+
+ my $size = -s "/proc/self/as"
+ or $class->_error_log("Fatal Error: /proc/self/as doesn't exist or is empty");
+ $size = int( $size / 1024 );
+
+ # return 0 for share, to avoid undef warnings
+ return ( $size, 0 );
}
-sub bsd_size_check {
- return (&BSD::Resource::getrusage())[2,3];
+# rss is in KB but ixrss is in BYTES.
+# This is true on at least FreeBSD, OpenBSD, & NetBSD - Phil Gollucci
+sub _bsd_size_check {
+ my @results = BSD::Resource::getrusage();
+ my $max_rss = $results[2];
+ my $max_ixrss = int ( $results[3] / 1024 );
+
+ return ( $max_rss, $max_ixrss );
}
-sub win32_size_check {
+sub _win32_size_check {
+ my $class = shift;
+
# get handle on current process
- my $GetCurrentProcess = new Win32::API('kernel32',
- 'GetCurrentProcess',
- [],
- 'I');
- my $hProcess = $GetCurrentProcess->Call();
+ my $get_current_process = Win32::API->new(
+ 'kernel32',
+ 'get_current_process',
+ [],
+ 'I'
+ );
+ my $proc = $get_current_process->Call();
-
# memory usage is bundled up in ProcessMemoryCounters structure
# populated by GetProcessMemoryInfo() win32 call
- my $DWORD = 'B32'; # 32 bits
- my $SIZE_T = 'I'; # unsigned integer
+ my $DWORD = 'B32'; # 32 bits
+ my $SIZE_T = 'I'; # unsigned integer
# build a buffer structure to populate
my $pmem_struct = "$DWORD" x 2 . "$SIZE_T" x 8;
- my $pProcessMemoryCounters = pack($pmem_struct, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
-
+ my $mem_counters
+ = pack( $pmem_struct, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
# GetProcessMemoryInfo is in "psapi.dll"
- my $GetProcessMemoryInfo = new Win32::API('psapi',
- 'GetProcessMemoryInfo',
- ['I', 'P', 'I'],
- 'I');
-
- my $bool = $GetProcessMemoryInfo->Call($hProcess,
- $pProcessMemoryCounters,
- length($pProcessMemoryCounters));
+ my $get_process_memory_info = new Win32::API(
+ 'psapi',
+ 'GetProcessMemoryInfo',
+ [ 'I', 'P', 'I' ],
+ 'I'
+ );
+
+ my $bool = $get_process_memory_info->Call(
+ $proc,
+ $mem_counters,
+ length $mem_counters,
+ );
# unpack ProcessMemoryCounters structure
- my ($cb,
- $PageFaultCount,
- $PeakWorkingSetSize,
- $WorkingSetSize,
- $QuotaPeakPagedPoolUsage,
- $QuotaPagedPoolUsage,
- $QuotaPeakNonPagedPoolUsage,
- $QuotaNonPagedPoolUsage,
- $PagefileUsage,
- $PeakPagefileUsage) = unpack($pmem_struct, $pProcessMemoryCounters);
+ my $peak_working_set_size =
+ ( unpack( $pmem_struct, $mem_counters ) )[2];
# only care about peak working set size
- my $size = int($PeakWorkingSetSize / 1024);
+ my $size = int( $peak_working_set_size / 1024 );
- return ($size, 0);
+ return ( $size, 0 );
}
+sub _perl_getppid { return getppid }
+sub _linux_getppid { return Linux::Pid::getppid() }
-sub exit_if_too_big {
- my $r = shift;
- return DECLINED if ($CHECK_EVERY_N_REQUESTS &&
- ($REQUEST_COUNT++ % $CHECK_EVERY_N_REQUESTS));
+{
+ # Deprecated APIs
- $START_TIME ||= time;
+ sub setmax {
+ my $class = __PACKAGE__;
- my($size, $share) = &$HOW_BIG_IS_IT();
+ $class->set_max_process_size(shift);
- if (($MAX_PROCESS_SIZE && $size > $MAX_PROCESS_SIZE)
- ||
- ($MIN_SHARE_SIZE && $share < $MIN_SHARE_SIZE)
- ||
- ($MAX_UNSHARED_SIZE && ($size - $share) > $MAX_UNSHARED_SIZE)) {
-
- # wake up! time to die.
- if ($WIN32 || (getppid > 1)) { # this is a child httpd
- my $e = time - $START_TIME;
- my $msg = "httpd process too big, exiting at SIZE=$size KB ";
- $msg .= " SHARE=$share KB " if ($share);
- $msg .= " REQUESTS=$REQUEST_COUNT LIFETIME=$e seconds";
- error_log($msg);
-
- if ($WIN32) {
- CORE::exit(-2); # child_terminate() is disabled in win32 Apache
- } else {
- $r->child_terminate();
- }
-
- } else { # this is the main httpd, whose parent is init?
- my $msg = "main process too big, SIZE=$size KB ";
- $msg .= " SHARE=$share KB" if ($share);
- error_log($msg);
- }
+ $class->add_cleanup_handler();
}
- return OK;
-}
-# setmax can be called from within a CGI/Registry script to tell the httpd
-# to exit if the CGI causes the process to grow too big.
-sub setmax {
- $MAX_PROCESS_SIZE = shift;
- Apache->request->post_connection(\&exit_if_too_big);
-}
+ sub setmin {
+ my $class = __PACKAGE__;
-sub setmin {
- $MIN_SHARE_SIZE = shift;
- Apache->request->post_connection(\&exit_if_too_big);
-}
+ $class->set_min_shared_size(shift);
-sub setmax_unshared {
- $MAX_UNSHARED_SIZE = shift;
- Apache->request->post_connection(\&exit_if_too_big);
-}
+ $class->add_cleanup_handler();
+ }
-sub handler {
- my $r = shift || Apache->request;
- if ($r->is_main()) {
- # we want to operate in a cleanup handler
- if ($r->current_callback eq 'PerlCleanupHandler') {
- exit_if_too_big($r);
- } else {
- $r->post_connection(\&exit_if_too_big);
- }
+ sub setmax_unshared {
+ my $class = __PACKAGE__;
+
+ $class->set_max_unshared_size(shift);
+
+ $class->add_cleanup_handler();
}
- return(DECLINED);
}
-sub error_log {
- print STDERR "[", scalar(localtime(time)), "] ($$) Apache::SizeLimit @_\n";
-}
1;
+
+__END__
+
+=head1 NAME
+
+Apache::SizeLimit - Because size does matter.
+
+=head1 SYNOPSIS
+
+ <Perl>
+ Apache::SizeLimit->set_max_process_size(150_000); # Max size in KB
+ Apache::SizeLimit->set_min_shared_size(10_000); # Min share in KB
+ Apache::SizeLimit->set_max_unshared_size(120_000); # Max unshared size in KB
+ </Perl>
+
+ PerlCleanupHandler Apache::SizeLimit
+
+=head1 DESCRIPTION
+
+******************************** NOIICE *******************
+
+ This version is only for httpd 1.x and mod_perl 1.x
+ series.
+
+ Future versions of this module may support both.
+
+ Currently, Apache2::SizeLimit is bundled with
+ mod_perl 2.x for that series.
+
+******************************** NOTICE *******************
+
+This module allows you to kill off Apache httpd processes if they grow
+too large. You can make the decision to kill a process based on its
+overall size, by setting a minimum limit on shared memory, or a
+maximum on unshared memory.
+
+You can set limits for each of these sizes, and if any limit is
+exceeded, the process will be killed.
+
+You can also limit the frequency that these sizes are checked so that
+this module only checks every N requests.
+
+This module is highly platform dependent, please read the
+L<PER-PLATFORM BEHAVIOR> section for details. It is possible that this
+module simply does not support your platform.
+
+=head1 API
+
+You can set set the size limits from a Perl module or script loaded by
+Apache by calling the appropriate class method on C<Apache::SizeLimit>:
+
+=over 4
+
+=item * Apache::SizeLimit->set_max_process_size($size)
+
+This sets the maximum size of the process, including both shared and
+unshared memory.
+
+=item * Apache::SizeLimit->set_max_unshared_size($size)
+
+This sets the maximum amount of I<unshared> memory the process can
+use.
+
+=item * Apache::SizeLimit->set_min_shared_size($size)
+
+This sets the minimum amount of shared memory the process must have.
+
+=back
+
+The two methods related to shared memory size are effectively a no-op
+if the module cannot determine the shared memory size for your
+platform. See L<PER-PLATFORM BEHAVIOR> for more details.
+
+=head2 Running the handler()
+
+There are several ways to make this module actually run the code to
+kill a process.
+
+The simplest is to make C<Apache::SizeLimit> a C<PerlCleanupHandler>
+in your Apache config:
+
+ PerlCleanupHandler Apache::SizeLimit
+
+This will ensure that C<< Apache::SizeLimit->handler() >> is run
+for all requests.
+
+If you want to combine this module with a cleanup handler of your own,
+make sure that C<Apache::SizeLimit> is the last handler run:
+
+ PerlCleanupHandler Apache::SizeLimit My::CleanupHandler
+
+Remember, mod_perl will run stacked handlers from right to left, as
+they're defined in your configuration.
+
+If you have some cleanup code you need to run, but stacked handlers
+aren't appropriate for your setup, you can also explicitly call the
+C<< Apache::SizeLimit->handler() >> function from your own cleanup
+handler:
+
+ package My::CleanupHandler
+
+ sub handler {
+ my $r = shift;
+
+ # Causes File::Temp to remove any temp dirs created during the
+ # request
+ File::Temp::cleanup();
+
+ return Apache::SizeLimit->handler($r);
+ }
+
+=over 4
+
+=item * Apache::SizeLimit->add_cleanup_handler($r)
+
+You can call this method inside a request to run
+C<Apache::SizeLimit>'s C<handler()> method for just that request. It's
+safe to call this method repeatedly -- the cleanup will only be run
+once per request.
+
+=back
+
+=head2 Checking Every N Requests
+
+Since checking the process size can take a few system calls on some
+platforms (e.g. linux), you may not want to check the process size for
+every request.
+
+=over 4
+
+=item * Apache::SizeLimit->set_check_interval($interval)
+
+Calling this causes C<Apache::SizeLimit> to only check the process
+size every C<$interval> requests. If you want this to affect all
+processes, make sure to call this during server startup.
+
+=back
+
+=head1 SHARED MEMORY OPTIONS
+
+In addition to simply checking the total size of a process, this
+module can factor in how much of the memory used by the process is
+actually being shared by copy-on-write. If you don't understand how
+memory is shared in this way, take a look at the mod_perl docs at
+http://perl.apache.org/docs/.
+
+You can take advantage of the shared memory information by setting a
+minimum shared size and/or a maximum unshared size. Experience on one
+heavily trafficked mod_perl site showed that setting maximum unshared
+size and leaving the others unset is the most effective policy. This
+is because it only kills off processes that are truly using too much
+physical RAM, allowing most processes to live longer and reducing the
+process churn rate.
+
+=head1 PER-PLATFORM BEHAVIOR
+
+This module is highly platform dependent, since finding the size of a
+process is different for each OS, and some platforms may not be
+supported. In particular, the limits on minimum shared memory and
+maximum shared memory are currently only supported on Linux and BSD.
+If you can contribute support for another OS, patches are very
+welcome.
+
+Currently supported OSes:
+
+=head2 linux
+
+For linux we read the process size out of F</proc/self/statm>. If you
+are worried about performance, you can consider using C<<
+Apache::SizeLimit->set_check_interval() >> to reduce how often this
+read happens.
+
+As of linux 2.6, F</proc/self/statm> does not report the amount of
+memory shared by the copy-on-write mechanism as shared memory. This
+means that decisions made based on shared memory as reported by that
+interface are inherently wrong.
+
+However, as of the 2.6.14 release of the kernel, there is
+F</proc/self/smaps> entry for each process. F</proc/self/smaps>
+reports various sizes for each memory segment of a process and allows
+us to count the amount of shared memory correctly.
+
+If C<Apache::SizeLimit> detects a kernel that supports
+F</proc/self/smaps> and the C<Linux::Smaps> module is installed it
+will use that module instead of F</proc/self/statm>.
+
+Reading F</proc/self/smaps> is expensive compared to
+F</proc/self/statm>. It must look at each page table entry of a
+process. Further, on multiprocessor systems the access is
+synchronized with spinlocks. Again, you might consider using C<<
+Apache::SizeLimit->set_check_interval() >>.
+
+=head3 Copy-on-write and Shared Memory
+
+The following example shows the effect of copy-on-write:
+
+ <Perl>
+ require Apache::SizeLimit;
+ package X;
+ use strict;
+ use Apache::Constants qw(OK);
+
+ my $x = "a" x (1024*1024);
+
+ sub handler {
+ my $r = shift;
+ my ($size, $shared) = $Apache::SizeLimit->_check_size();
+ $x =~ tr/a/b/;
+ my ($size2, $shared2) = $Apache::SizeLimit->_check_size();
+ $r->content_type('text/plain');
+ $r->print("1: size=$size shared=$shared\n");
+ $r->print("2: size=$size2 shared=$shared2\n");
+ return OK;
+ }
+ </Perl>
+
+ <Location /X>
+ SetHandler modperl
+ PerlResponseHandler X
+ </Location>
+
+The parent Apache process allocates memory for the string in
+C<$x>. The C<tr>-command then overwrites all "a" with "b" if the
+handler is called with an argument. This write is done in place, thus,
+the process size doesn't change. Only C<$x> is not shared anymore by
+means of copy-on-write between the parent and the child.
+
+If F</proc/self/smaps> is available curl shows:
+
+ r2@s93:~/work/mp2> curl http://localhost:8181/X
+ 1: size=13452 shared=7456
+ 2: size=13452 shared=6432
+
+Shared memory has lost 1024 kB. The process' overall size remains unchanged.
+
+Without F</proc/self/smaps> it says:
+
+ r2@s93:~/work/mp2> curl http://localhost:8181/X
+ 1: size=13052 shared=3628
+ 2: size=13052 shared=3636
+
+One can see the kernel lies about the shared memory. It simply doesn't
+count copy-on-write pages as shared.
+
+=head2 solaris 2.6 and above
+
+For solaris we simply retrieve the size of F</proc/self/as>, which
+contains the address-space image of the process, and convert to KB.
+Shared memory calculations are not supported.
+
+NOTE: This is only known to work for solaris 2.6 and above. Evidently
+the F</proc> filesystem has changed between 2.5.1 and 2.6. Can anyone
+confirm or deny?
+
+=head2 BSD (and OSX)
+
+Uses C<BSD::Resource::getrusage()> to determine process size. This is
+pretty efficient (a lot more efficient than reading it from the
+F</proc> fs anyway).
+
+According to recent tests on OSX (July, 2006), C<BSD::Resource> simply
+reports zero for process and shared size on that platform, so OSX is
+not supported by C<Apache::SizeLimit>.
+
+=head2 AIX?
+
+Uses C<BSD::Resource::getrusage()> to determine process size. Not
+sure if the shared memory calculations will work or not. AIX users?
+
+=head2 Win32
+
+Uses C<Win32::API> to access process memory information.
+C<Win32::API> can be installed under ActiveState perl using the
+supplied ppm utility.
+
+=head2 Everything Else
+
+If your platform is not supported, then please send a patch to check
+the process size. The more portable/efficient/correct the solution the
+better, of course.
+
+=head1 ABOUT THIS MODULE
+
+This module was written in response to questions on the mod_perl
+mailing list on how to tell the httpd process to exit if it gets too
+big.
+
+Actually, there are two big reasons your httpd children will grow.
+First, your code could have a bug that causes the process to increase
+in size very quickly. Second, you could just be doing operations that
+require a lot of memory for each request. Since Perl does not give
+memory back to the system after using it, the process size can grow
+quite large.
+
+This module will not really help you with the first problem. For that
+you should probably look into C<Apache::Resource> or some other means
+of setting a limit on the data size of your program. BSD-ish systems
+have C<setrlimit()>, which will kill your memory gobbling processes.
+However, it is a little violent, terminating your process in
+mid-request.
+
+This module attempts to solve the second situation, where your process
+slowly grows over time. It checks memory usage after every request,
+and if it exceeds a threshold, exits gracefully.
+
+By using this module, you should be able to discontinue using the
+Apache configuration directive B<MaxRequestsPerChild>, although for
+some folks, using both in combination does the job.
+
+=head1 DEPRECATED APIS
+
+Previous versions of this module documented three globals for defining
+memory size limits:
+
+=over 4
+
+=item * $Apache::SizeLimit::MAX_PROCESS_SIZE
+
+=item * $Apache::SizeLimit::MIN_SHARE_SIZE
+
+=item * $Apache::SizeLimit::MAX_UNSHARED_SIZE
+
+=item * $Apache::SizeLimit::CHECK_EVERY_N_REQUESTS
+
+=item * $Apache::SizeLimit::USE_SMAPS
+
+=back
+
+Direct use of these globals is deprecated, but will continue to work
+for the foreseeable future.
+
+It also documented three functions for use from registry scripts:
+
+=over 4
+
+=item * Apache::SizeLimit::setmax()
+
+=item * Apache::SizeLimit::setmin()
+
+=item * Apache::SizeLimit::setmax_unshared()
+
+=back
+
+Besides setting the appropriate limit, these functions I<also> add a
+cleanup handler to the current request.
+
=head1 AUTHOR
Doug Bagley <do...@bagley.org>, channeling Procrustes.
@@ -344,5 +694,8 @@
Matt Phillips <mp...@virage.com> and Mohamed Hendawi
<mh...@virage.com>: Win32 support
+
+Dave Rolsky <au...@urth.org>, maintenance and fixes outside of
+mod_perl tree (0.9+).
=cut