Author: pgollucci Date: Fri Jun 23 23:57:52 2006 New Revision: 416874 URL: http://svn.apache.org/viewvc?rev=416874&view=rev Log: o overwrite relavent files with https://svn.urth.org/svn/Apache-SizeLimit o update MANIFEST to be correct o append Chnages list
Added: perl/Apache-SizeLimit/trunk/MANIFEST.SKIP perl/Apache-SizeLimit/trunk/t/TEST.PL perl/Apache-SizeLimit/trunk/t/pod.t perl/Apache-SizeLimit/trunk/t/response/ perl/Apache-SizeLimit/trunk/t/response/TestApache/ perl/Apache-SizeLimit/trunk/t/response/TestApache/basic.pm Modified: perl/Apache-SizeLimit/trunk/Changes perl/Apache-SizeLimit/trunk/MANIFEST perl/Apache-SizeLimit/trunk/Makefile.PL perl/Apache-SizeLimit/trunk/lib/Apache/SizeLimit.pm Modified: perl/Apache-SizeLimit/trunk/Changes URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/Changes?rev=416874&r1=416873&r2=416874&view=diff ============================================================================== --- perl/Apache-SizeLimit/trunk/Changes (original) +++ perl/Apache-SizeLimit/trunk/Changes Fri Jun 23 23:57:52 2006 @@ -1,6 +1,40 @@ -Revision history for Perl extension Apache-SizeLimit. +=head1 NAME -0.01 Fri Jun 23 22:46:53 2006 - - original version; created by h2xs 1.23 with options - -X -A -n Apache-SizeLimit +Changes - Apache::SizeLimit change logfile + +=head1 CHANGES + +=over 2 + +=item 0.04-dev +Copied from the mod_perl 1 core for an independent CPAN release. +[Philip M. Gollucci <[EMAIL PROTECTED]>] + +Added support for using Linux::Smaps (on Linux only, obviously) to +get much more accurate shared memory numbers on 2.6.x kernels. Taken +from Apache2::SizeLimit. +[Dave Rolsky <[EMAIL PROTECTED]> + +Added support for using Linux::Pid to get the parent pid on +Linux. This fixes a long-standing bug that caused this module to never +actually kill a process when using Perl 5.8.1+ on Linux. +[Dave Rolsky <[EMAIL PROTECTED]> + +Lots of work on the docs. +[Dave Rolsky <[EMAIL PROTECTED]> + +When calling C<setmax()>, C<setmin()>, and C<setmax_unshared()>, +only add Apache::SizeLimit as a cleanup handler once, not once for +each function call. Taken from Apache2::SizeLimit. +[Dave Rolsky <[EMAIL PROTECTED]> + +Deprecated direct use of globals to set memory limits. +[Dave Rolsky <[EMAIL PROTECTED]> + +=item 0.01 Fri Jun 23 22:46:53 2006 +original version; created by h2xs 1.23 with options +-X -A -n Apache-SizeLimit +[Philip M. Gollucci <[EMAIL PROTECTED]>] + +=back Modified: perl/Apache-SizeLimit/trunk/MANIFEST URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/MANIFEST?rev=416874&r1=416873&r2=416874&view=diff ============================================================================== --- perl/Apache-SizeLimit/trunk/MANIFEST (original) +++ perl/Apache-SizeLimit/trunk/MANIFEST Fri Jun 23 23:57:52 2006 @@ -1,6 +1,13 @@ -Changes -Makefile.PL -MANIFEST -README -t/Apache-SizeLimit.t -lib/Apache-SizeLimit.pm +./Changes +./INSTALL +./LICENSE +./MANIFEST +./MANIFEST.SKIP +./Makefile.PL +./README +./RELEASE +./SUPPORT +./lib/Apache/SizeLimit.pm +./t/TEST.PL +./t/pod.t +./t/response/TestApache/basic.pm Added: perl/Apache-SizeLimit/trunk/MANIFEST.SKIP URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/MANIFEST.SKIP?rev=416874&view=auto ============================================================================== --- perl/Apache-SizeLimit/trunk/MANIFEST.SKIP (added) +++ perl/Apache-SizeLimit/trunk/MANIFEST.SKIP Fri Jun 23 23:57:52 2006 @@ -0,0 +1,16 @@ +.*CVS.* +^Makefile$ +\.\#.* +~$ +MANIFEST.SKIP +.*\.tar\.gz +.bak$ +^blib/ +.*\.svn.* +\#.* +t/TEST.* +t/apache.* +t/conf.* +t/htdocs.* +t/logs.* +t/response.* \ No newline at end of file Modified: perl/Apache-SizeLimit/trunk/Makefile.PL URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/Makefile.PL?rev=416874&r1=416873&r2=416874&view=diff ============================================================================== --- perl/Apache-SizeLimit/trunk/Makefile.PL (original) +++ perl/Apache-SizeLimit/trunk/Makefile.PL Fri Jun 23 23:57:52 2006 @@ -1,12 +1,37 @@ -use 5.008008; +use strict; + +use Config; use ExtUtils::MakeMaker; -# See lib/ExtUtils/MakeMaker.pm for details of how to influence -# the contents of the Makefile that is written. -WriteMakefile( - NAME => 'Apache-SizeLimit', - VERSION_FROM => 'lib/Apache-SizeLimit.pm', # finds $VERSION - PREREQ_PM => {}, # e.g., Module::Name => 1.1 - ($] >= 5.005 ? ## Add these new keywords supported since 5.005 - (ABSTRACT_FROM => 'lib/Apache-SizeLimit.pm', # retrieve abstract from module - AUTHOR => 'Philip M. Gollucci <[EMAIL PROTECTED]>') : ()), -); + +my %prereqs = ( mod_perl => 0 ); + +unless ( $ARGV[0] eq '--dist' ) { + if ( $Config{'osname'} eq 'linux' ) { + $prereqs{'Linux::Pid'} = 0; + if ( -e '/proc/self/smaps' ) { + $prereqs{'Linux::Smaps'} = 0; + } + } + elsif ( $Config{'osname'} =~ /(bsd|aix|darwin)/i ) { + $prereqs{'BSD::Resource'} = 0; + } + elsif ( $Config{'osname'} eq 'MSWin32' ) { + $prereqs{'Win32::API'} = 0; + } + + if ( -f 't/apache/basic.t' + && eval { require Apache::TestMM } ) { + $prereqs{'Apache::Test'} = 0; + + Apache::TestMM::filter_args(); + Apache::TestMM::generate_script('t/TEST'); + } +} + +WriteMakefile( VERSION_FROM => "lib/Apache/SizeLimit.pm", + NAME => "Apache::SizeLimit", + PREREQ_PM => \%prereqs, + ABSTRACT_FROM => 'lib/Apache/SizeLimit.pm', + AUTHOR => 'Dave Rolsky, <[EMAIL PROTECTED]>', + clean => { FILES => 't/TEST t/logs t/htdocs t/conf' }, + ); Modified: perl/Apache-SizeLimit/trunk/lib/Apache/SizeLimit.pm URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/lib/Apache/SizeLimit.pm?rev=416874&r1=416873&r2=416874&view=diff ============================================================================== --- perl/Apache-SizeLimit/trunk/lib/Apache/SizeLimit.pm (original) +++ perl/Apache-SizeLimit/trunk/lib/Apache/SizeLimit.pm Fri Jun 23 23:57:52 2006 @@ -1,248 +1,149 @@ package Apache::SizeLimit; -=head1 NAME - -Apache::SizeLimit - Because size does matter. - -=head1 SYNOPSIS - -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 - - # in your httpd.conf: - PerlCleanupHandler Apache::SizeLimit - -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: - - $Apache::SizeLimit::CHECK_EVERY_N_REQUESTS = 2; - -This will only check the process size every other time the process size -checker is called. - -=head1 DESCRIPTION - -This module is highly platform dependent, please read the CAVEATS section. - -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, -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. - -=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 Guide at -http://perl.apache.org/guide/. - -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 CAVEATS - -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. - -Currently supported OSes: - -=over 4 - -=item linux - -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. - -=item solaris 2.6 and above - -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. - -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? - -=item *bsd* - -Uses BSD::Resource::getrusage() to determine process size. This is pretty -efficient (a lot more efficient than reading it from the /proc fs anyway). +use Apache::Constants qw(DECLINED OK); +use Config; +use strict; +use vars qw( + $VERSION + $MAX_PROCESS_SIZE + $REQUEST_COUNT + $CHECK_EVERY_N_REQUESTS + $MIN_SHARE_SIZE + $MAX_UNSHARED_SIZE + $START_TIME + $IS_WIN32 + $USE_SMAPS +); -=item AIX? +$VERSION = '0.04'; +$CHECK_EVERY_N_REQUESTS = 1; +$REQUEST_COUNT = 1; +$MAX_PROCESS_SIZE = 0; +$MIN_SHARE_SIZE = 0; +$MAX_UNSHARED_SIZE = 0; +$IS_WIN32 = 0; +$USE_SMAPS = 1; -Uses BSD::Resource::getrusage() to determine process size. Not sure if the -shared memory calculations will work or not. AIX users? +BEGIN { -=item Win32 + # decide at compile time how to check for a process' memory size. + if ( $Config{'osname'} eq 'solaris' + && $Config{'osvers'} >= 2.6 ) { + *check_size = \&_solaris_2_6_size_check; + *real_getppid = \&_perl_getppid; + } + elsif ( $Config{'osname'} eq 'linux' ) { + eval { require Linux::Pid } + or die "You must install Linux::Pid for Apache::SizeLimit to work on your platform."; -Uses Win32::API to access process memory information. Win32::API can be -installed under ActiveState perl using the supplied ppm utility. + *real_getppid = \&_linux_getppid; -=back + if ( eval { require Linux::Smaps } && Linux::Smaps->new($$) ) { + *check_size = \&_linux_smaps_size_check; + } + else { + $USE_SMAPS = 0; + *check_size = \&_linux_size_check; + } + } + elsif ( $Config{'osname'} =~ /(?:bsd|aix|darwin)/i ) { -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. + # will getrusage work on all BSDs? I should hope so. + eval "require BSD::Resource;" + or die + "You must install BSD::Resource for Apache::SizeLimit to work on your platform."; -=head1 TODO + *check_size = \&_bsd_size_check; + *real_getppid = \&_perl_getppid; + } + elsif ( $Config{'osname'} eq 'MSWin32' ) { + eval { require Win32::API } + or die + "You must install Win32::API for Apache::SizeLimit to work on your platform."; -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. + $IS_WIN32 = 1; -If Apache was started in non-forking mode, should hitting the size limit -cause the process to exit? + *check_size = \&_win32_size_check; + *real_getppid = \&_perl_getppid; + } + else { + die "Apache::SizeLimit is not implemented on your platform."; + } +} -=cut +sub _linux_smaps_size_check { + goto &linux_size_check unless $USE_SMAPS; -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); + my $s = Linux::Smaps->new($$)->all; + return ($s->size, $s->shared_clean + $s->shared_dirty); +} -$VERSION = '0.03'; -$CHECK_EVERY_N_REQUESTS = 1; -$REQUEST_COUNT = 1; -$MAX_PROCESS_SIZE = 0; -$MIN_SHARE_SIZE = 0; -$MAX_UNSHARED_SIZE = 0; +sub _linux_size_check { + my ( $size, $resident, $share ) = ( 0, 0, 0 ); -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."; - } - } else { - die "Apache::SizeLimit not implemented on your platform."; + if ( open my $fh, '<', '/proc/self/statm' ) { + ( $size, $resident, $share ) = split /\s/, scalar <$fh>; + close $fh; } -} - -# 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"); + else { + _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 $size = -s "/proc/self/as" + or _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]; +sub _bsd_size_check { + return ( BSD::Resource::getrusage() )[ 2, 3 ]; } -sub win32_size_check { +sub _win32_size_check { # get handle on current process - my $GetCurrentProcess = new Win32::API('kernel32', - 'GetCurrentProcess', - [], - 'I'); + my $GetCurrentProcess = Win32::API->new( + 'kernel32', + 'GetCurrentProcess', + [], + 'I' + ); my $hProcess = $GetCurrentProcess->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 $pProcessMemoryCounters + = 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 $GetProcessMemoryInfo = new Win32::API( + 'psapi', + 'GetProcessMemoryInfo', + [ 'I', 'P', 'I' ], + 'I' + ); + + my $bool = $GetProcessMemoryInfo->Call( + $hProcess, + $pProcessMemoryCounters, + length($pProcessMemoryCounters) + ); # unpack ProcessMemoryCounters structure - my ($cb, - $PageFaultCount, + my ( + $cb, + $PageFaultCount, $PeakWorkingSetSize, $WorkingSetSize, $QuotaPeakPagedPoolUsage, @@ -250,49 +151,56 @@ $QuotaPeakNonPagedPoolUsage, $QuotaNonPagedPoolUsage, $PagefileUsage, - $PeakPagefileUsage) = unpack($pmem_struct, $pProcessMemoryCounters); + $PeakPagefileUsage + ) = unpack( $pmem_struct, $pProcessMemoryCounters ); # only care about peak working set size - my $size = int($PeakWorkingSetSize / 1024); + my $size = int( $PeakWorkingSetSize / 1024 ); - return ($size, 0); + return ( $size, 0 ); } +sub _perl_getppid { return getppid } +sub _linux_getppid { return Linux::Pid::getppid() } -sub exit_if_too_big { +sub _exit_if_too_big { my $r = shift; - return DECLINED if ($CHECK_EVERY_N_REQUESTS && - ($REQUEST_COUNT++ % $CHECK_EVERY_N_REQUESTS)); + + return DECLINED + if ( $CHECK_EVERY_N_REQUESTS + && ( $REQUEST_COUNT++ % $CHECK_EVERY_N_REQUESTS ) ); $START_TIME ||= time; - my($size, $share) = &$HOW_BIG_IS_IT(); + my ( $size, $share ) = check_size(); + my $unshared = $size - $share; - 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); - } + if ( ( $MAX_PROCESS_SIZE && $size > $MAX_PROCESS_SIZE ) + || ( $MIN_SHARE_SIZE && $share < $MIN_SHARE_SIZE ) + || ( $MAX_UNSHARED_SIZE && $unshared > $MAX_UNSHARED_SIZE ) ) { + # wake up! time to die. + if ( $IS_WIN32 || real_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"; + _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); + _error_log($msg); + } } return OK; } @@ -301,38 +209,345 @@ # 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); + + _set_post_conn(); } sub setmin { $MIN_SHARE_SIZE = shift; - Apache->request->post_connection(\&exit_if_too_big); + + _set_post_conn(); } sub setmax_unshared { $MAX_UNSHARED_SIZE = shift; - Apache->request->post_connection(\&exit_if_too_big); + + _set_post_conn(); +} + +sub _set_post_conn { + my $r = Apache->request + or return; + + return if $Apache::Server::Starting || $Apache::Server::ReStarting; + return if $r->pnotes('size_limit_cleanup'); + + $r->post_connection( \&_exit_if_too_big ); + $r->pnotes( size_limit_cleanup => 1 ); } 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); - } + + return DECLINED unless $r->is_main(); + + # we want to operate in a cleanup handler + if ( $r->current_callback eq 'PerlCleanupHandler' ) { + return _exit_if_too_big($r); + } + else { + $r->post_connection( \&_exit_if_too_big ); } - return(DECLINED); + + return DECLINED; } -sub error_log { - print STDERR "[", scalar(localtime(time)), "] ($$) Apache::SizeLimit @_\n"; +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::MAX_UNSHARED_SIZE = 120000; # 120MB + </Perl> + + PerlCleanupHandler Apache::SizeLimit + +=head1 DESCRIPTION + +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 not +met, 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 CAVEATS +section. + +=head1 API + +You can set set the size limits from a Perl module or script loaded by +Apache: + + use Apache::SizeLimit; + + Apache::SizeLimit::setmax(150_000); # Max size in KB + Apache::SizeLimit::setmin(10_000); # Min share in KB + Apache::SizeLimit::setmax_unshared(120_000); # Max unshared size in KB + +Then in your Apache configuration, make Apache::SizeLimit a +C<PerlCleanupHandler>: + + PerlCleanupHandler Apache::SizeLimit + +If you want to use C<Apache::SizeLimit> from a registry script, you +must call one of the above functions for every request: + + use Apache::SizeLimit + + main(); + + sub { + Apache::SizeLimit::setmax(150_000); + + # handle request + }; + +Calling any one of C<setmax()>, C<setmin()>, or C<setmax_unshared()> +will install C<Apache::SizeLimit> as a cleanup handler, if it's not +already installed. + +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. + +You can explicitly call the C<Apache::SizeLimit::handler()> function +from your own handler: + + package My::CleanupHandler + + sub handler { + my $r = shift; + + # do my thing + + return Apache::SizeLimit::handler($r); + } + +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, simple set the +C<$Apache::SizeLimit::CHECK_EVERY_N_REQUESTS> global. + + $Apache::SizeLimit::CHECK_EVERY_N_REQUESTS = 2; + +Now C<Apache::SizeLimit> will only check the process size on every +other request. + +=head2 Deprecated API + +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 + +=back + +Direct use of these globals is deprecated, but will continue to work +for the foreseeable future. + +=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 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 Guide at +http://perl.apache.org/guide/. + +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 CAVEATS + +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: + +=over 4 + +=item linux + +For linux we read the process size out of F</proc/self/statm>. 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. + +Since linux 2.6 F</proc/self/statm> does not report the amount of +memory shared by the copy-on-write mechanism as shared memory. Hence +decisions made on the basis of C<MAX_UNSHARED_SIZE> or +C<MIN_SHARE_SIZE> are inherently wrong. + +To correct this situation, 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 if the C<Linux::Smaps> module is installed it +will use them instead of F</proc/self/statm>. You can prevent +C<Apache::SizeLimit> from using F</proc/self/smaps> and turn on the +old behaviour by setting C<$Apache::SizeLimit::USE_SMAPS> to 0. + +C<Apache::SizeLimit> itself will C<$Apache::SizeLimit::USE_SMAPS> to 0 +if it cannot load C<Linux::Smaps> or if your kernel does not support +F</proc/self/smaps>. Thus, you can check it to determine what is +actually used. + +NOTE: 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. Hence, you are encouraged to set the C<CHECK_EVERY_N_REQUESTS> +option. + +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 allocates a megabyte 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. + +=item 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? + +=item *bsd* + +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). + +=item AIX? + +Uses C<BSD::Resource::getrusage()> to determine process size. Not +sure if the shared memory calculations will work or not. AIX users? + +=item Win32 + +Uses C<Win32::API> to access process memory information. +C<Win32::API> can be installed under ActiveState perl using the +supplied ppm utility. + +=back + +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 AUTHOR Doug Bagley <[EMAIL PROTECTED]>, channeling Procrustes. @@ -345,4 +560,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.04+). + =cut + Added: perl/Apache-SizeLimit/trunk/t/TEST.PL URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/t/TEST.PL?rev=416874&view=auto ============================================================================== --- perl/Apache-SizeLimit/trunk/t/TEST.PL (added) +++ perl/Apache-SizeLimit/trunk/t/TEST.PL Fri Jun 23 23:57:52 2006 @@ -0,0 +1,8 @@ +use strict; +use warnings; + +use lib qw(lib); + +use Apache::TestRunPerl (); + +Apache::TestRunPerl->new->run(@ARGV); Added: perl/Apache-SizeLimit/trunk/t/pod.t URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/t/pod.t?rev=416874&view=auto ============================================================================== --- perl/Apache-SizeLimit/trunk/t/pod.t (added) +++ perl/Apache-SizeLimit/trunk/t/pod.t Fri Jun 23 23:57:52 2006 @@ -0,0 +1,4 @@ +use Test::More; +eval "use Test::Pod 1.14"; +plan skip_all => "Test::Pod 1.14 required for testing POD" if $@; +all_pod_files_ok(); Added: perl/Apache-SizeLimit/trunk/t/response/TestApache/basic.pm URL: http://svn.apache.org/viewvc/perl/Apache-SizeLimit/trunk/t/response/TestApache/basic.pm?rev=416874&view=auto ============================================================================== --- perl/Apache-SizeLimit/trunk/t/response/TestApache/basic.pm (added) +++ perl/Apache-SizeLimit/trunk/t/response/TestApache/basic.pm Fri Jun 23 23:57:52 2006 @@ -0,0 +1,50 @@ +package TestApache::basic; + +use strict; +use warnings; + +use Test::More; + +use Apache::Constants qw(OK); +use Apache::SizeLimit; + + +sub handler { + my $r = shift; + + Test::Builder->new->output(*STDOUT); + Test::Builder->new->failure_output(*STDOUT); + + $r->content_type('text/plain'); + $r->send_http_header(); + + plan tests => 3; + + Apache::SizeLimit::setmax( 100_000 ); + Apache::SizeLimit::setmin( 1 ); + + ok( $r->pnotes('size_limit_cleanup'), 'Set size_limit_cleanup in pnotes' ); + + my ( $size, $shared ) = Apache::SizeLimit::check_size(); + cmp_ok( $size, '>', 0, 'proc size is reported > 0' ); + + cmp_ok( Apache::SizeLimit::real_getppid(), '>', 1, + 'real_getppid() > 1' ); + + return OK; +} + +my $count = 1; +sub _test { + my $ok = shift; + my $desc = shift; + my $r = shift; + + my $string = $ok ? 'ok' : 'not ok'; + $r->print( "$string $count - $desc\n" ); + + $count++; +} + + +1;