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: + + [EMAIL PROTECTED]:~/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: + + [EMAIL PROTECTED]:~/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 <[EMAIL PROTECTED]>, channeling Procrustes. @@ -344,5 +694,8 @@ Matt Phillips <[EMAIL PROTECTED]> and Mohamed Hendawi <[EMAIL PROTECTED]>: Win32 support + +Dave Rolsky <[EMAIL PROTECTED]>, maintenance and fixes outside of +mod_perl tree (0.9+). =cut