Author: pgollucci Date: Mon Mar 26 07:16:38 2007 New Revision: 522521 URL: http://svn.apache.org/viewvc?view=rev&rev=522521 Log: pulled the seperate A-SL back out and restored lib/Apache/[EMAIL PROTECTED]
(will restore this after the 1.30 security release) Added: perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm Modified: perl/modperl/branches/1.x/ (props changed) perl/modperl/branches/1.x/Changes perl/modperl/branches/1.x/Makefile.PL Propchange: perl/modperl/branches/1.x/ ------------------------------------------------------------------------------ --- svn:externals (original) +++ svn:externals Mon Mar 26 07:16:38 2007 @@ -1,2 +1 @@ -Apache-SizeLimit https://svn.apache.org/repos/asf/perl/Apache-SizeLimit/trunk Modified: perl/modperl/branches/1.x/Changes URL: http://svn.apache.org/viewvc/perl/modperl/branches/1.x/Changes?view=diff&rev=522521&r1=522520&r2=522521 ============================================================================== --- perl/modperl/branches/1.x/Changes (original) +++ perl/modperl/branches/1.x/Changes Mon Mar 26 07:16:38 2007 @@ -16,11 +16,6 @@ reported by Alex Solovey [Randal L. Schwartz <merlyn@stonehenge.com>, Fred Moyer <[EMAIL PROTECTED]>] -Pull in the new Apache-SizeLimit from -http://svn.apache.org/repos/asf/perl/Apache-SizeLimit/trunk -and obsolete the previous lib/Apache/SizeLimit.pm. -[Philip M. Gollucci] - Fix an Apache::(Registry|PerlRun) bug caused by special characters in the url [EMAIL PROTECTED] Modified: perl/modperl/branches/1.x/Makefile.PL URL: http://svn.apache.org/viewvc/perl/modperl/branches/1.x/Makefile.PL?view=diff&rev=522521&r1=522520&r2=522521 ============================================================================== --- perl/modperl/branches/1.x/Makefile.PL (original) +++ perl/modperl/branches/1.x/Makefile.PL Mon Mar 26 07:16:38 2007 @@ -1531,7 +1531,7 @@ q( -test: pure_all start_httpd run_tests kill_httpd run_apache_sizelimit_tests +test: pure_all start_httpd run_tests kill_httpd ); my $have_so = $USE_DSO || ($APACI_ARGS =~ /--enable-shared=/); @@ -1570,10 +1570,6 @@ run_tests: $(FULLPERL) $(MP_TEST_SCRIPT) $(TEST_VERBOSE) - -run_apache_sizelimit_tests: - @(echo ; echo ; echo) - cd Apache-SizeLimit ; $(MAKE) test ), $my_test, Added: 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=auto&rev=522521 ============================================================================== --- perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm (added) +++ perl/modperl/branches/1.x/lib/Apache/SizeLimit.pm Mon Mar 26 07:16:38 2007 @@ -0,0 +1,348 @@ +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). + +=item AIX? + +Uses BSD::Resource::getrusage() to determine process size. Not sure if the +shared memory calculations will work or not. AIX users? + +=item Win32 + +Uses Win32::API to access process memory information. Win32::API can be +installed under ActiveState perl using the supplied ppm utility. + +=back + +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. + +=head1 TODO + +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. + +If Apache was started in non-forking mode, should hitting the size limit +cause the process to exit? + +=cut + +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; + +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."; + } +} + +# 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"); + } + # linux on intel x86 has 4KB page size... + 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 bsd_size_check { + return (&BSD::Resource::getrusage())[2,3]; +} + +sub win32_size_check { + # get handle on current process + my $GetCurrentProcess = new Win32::API('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 + + # 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); + + # GetProcessMemoryInfo is in "psapi.dll" + 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, + $PeakWorkingSetSize, + $WorkingSetSize, + $QuotaPeakPagedPoolUsage, + $QuotaPagedPoolUsage, + $QuotaPeakNonPagedPoolUsage, + $QuotaNonPagedPoolUsage, + $PagefileUsage, + $PeakPagefileUsage) = unpack($pmem_struct, $pProcessMemoryCounters); + + # only care about peak working set size + my $size = int($PeakWorkingSetSize / 1024); + + return ($size, 0); +} + + +sub exit_if_too_big { + my $r = shift; + return DECLINED if ($CHECK_EVERY_N_REQUESTS && + ($REQUEST_COUNT++ % $CHECK_EVERY_N_REQUESTS)); + + $START_TIME ||= time; + + my($size, $share) = &$HOW_BIG_IS_IT(); + + 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); + } + } + 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 { + $MIN_SHARE_SIZE = shift; + Apache->request->post_connection(\&exit_if_too_big); +} + +sub setmax_unshared { + $MAX_UNSHARED_SIZE = shift; + Apache->request->post_connection(\&exit_if_too_big); +} + +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); +} + +sub error_log { + print STDERR "[", scalar(localtime(time)), "] ($$) Apache::SizeLimit @_\n"; +} + +1; + +=head1 AUTHOR + +Doug Bagley <[EMAIL PROTECTED]>, channeling Procrustes. + +Brian Moseley <[EMAIL PROTECTED]>: Solaris 2.6 support + +Doug Steinwand and Perrin Harkins <[EMAIL PROTECTED]>: added support + for shared memory and additional diagnostic info + +Matt Phillips <[EMAIL PROTECTED]> and Mohamed Hendawi +<[EMAIL PROTECTED]>: Win32 support + +=cut