Author: stas
Date: Mon Dec  6 08:37:18 2004
New Revision: 109987

URL: http://svn.apache.org/viewcvs?view=rev&rev=109987
Log:
Apache::SizeLimit ported
Contributed by:         Perrin Harkins <perrin elem.com>

Added:
   perl/modperl/trunk/lib/Apache/SizeLimit.pm
Modified:
   perl/modperl/trunk/Changes

Modified: perl/modperl/trunk/Changes
Url: 
http://svn.apache.org/viewcvs/perl/modperl/trunk/Changes?view=diff&rev=109987&p1=perl/modperl/trunk/Changes&r1=109986&p2=perl/modperl/trunk/Changes&r2=109987
==============================================================================
--- perl/modperl/trunk/Changes  (original)
+++ perl/modperl/trunk/Changes  Mon Dec  6 08:37:18 2004
@@ -12,6 +12,8 @@
 
 =item 1.99_18-dev
 
+Apache::SizeLimit ported [Perrin Harkins <perrin elem.com>]
+
 create a new subpool modperl_server_user_pool (from
 modperl_server_pool), which is handed to users via
 Apache::ServerUtil::base_server_pool(). This ensures that

Added: perl/modperl/trunk/lib/Apache/SizeLimit.pm
Url: 
http://svn.apache.org/viewcvs/perl/modperl/trunk/lib/Apache/SizeLimit.pm?view=auto&rev=109987
==============================================================================
--- (empty file)
+++ perl/modperl/trunk/lib/Apache/SizeLimit.pm  Mon Dec  6 08:37:18 2004
@@ -0,0 +1,237 @@
+# Copyright 2003-2004 The Apache Software Foundation
+#
+# 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;
+
+use strict;
+use warnings FATAL => 'all';
+
+use mod_perl 1.99;
+
+use Apache::RequestRec ();
+use Apache::RequestUtil ();
+use Apache::Connection ();
+use APR::Pool ();
+use ModPerl::Util ();
+
+use Config;
+
+use constant WIN32   => $^O eq 'MSWin32';
+use constant SOLARIS => $^O eq 'solaris';
+use constant LINUX   => $^O eq 'linux';
+
+use Apache::Const -compile => qw(OK DECLINED);
+
+our $VERSION = '0.04';
+
+our $CHECK_EVERY_N_REQUESTS = 1;
+our $REQUEST_COUNT          = 1;
+our $MAX_PROCESS_SIZE       = 0;
+our $MIN_SHARE_SIZE         = 0;
+our $MAX_UNSHARED_SIZE      = 0;
+
+our ($HOW_BIG_IS_IT, $START_TIME);
+
+BEGIN {
+
+    # decide at compile time how to check for a process' memory size.
+    if (SOLARIS && $Config{'osvers'} >= 2.6) {
+
+        $HOW_BIG_IS_IT = \&solaris_2_6_size_check;
+
+    } elsif (LINUX) {
+
+        $HOW_BIG_IS_IT = \&linux_size_check;
+
+    } elsif ( $Config{'osname'} =~ /(bsd|aix)/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 (WIN32) {
+
+        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);
+
+    my $file = "/proc/self/statm";
+    if (open my $fh, "<$file") {
+        ($size, $resident, $share) = split /\s/, scalar <$fh>;
+        close $fh;
+    } else {
+        error_log("Fatal Error: couldn't access $file");
+    }
+
+    # linux on intel x86 has 4KB page size...
+    return ($size * 4, $share * 4);
+}
+
+sub solaris_2_6_size_check {
+    my $file = "/proc/self/as";
+    my $size = -s $file
+        or &error_log("Fatal Error: $file doesn't exist or is empty");
+    $size = int($size / 1024); # in Kb
+    return ($size, 0);
+}
+
+sub bsd_size_check {
+    return (BSD::Resource::getrusage())[ 2, 3 ];
+}
+
+sub win32_size_check {
+
+    # get handle on current process
+    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
+
+    # 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 = Win32::API->new('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;
+
+    #warn "Apache::Size::Limit exit sub called";
+
+    return Apache::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);
+
+            $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 Apache::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;
+    my $r = Apache->request();
+    unless ($r->pnotes('size_limit_cleanup')) {
+        $r->connection->pool->cleanup_register(\&exit_if_too_big, $r);
+        $r->pnotes('size_limit_cleanup', 1);
+    }
+}
+
+sub setmin {
+    $MIN_SHARE_SIZE = shift;
+    my $r = Apache->request();
+    unless ($r->pnotes('size_limit_cleanup')) {
+        $r->connection->pool->cleanup_register(\&exit_if_too_big, $r);
+        $r->pnotes('size_limit_cleanup', 1);
+    }
+}
+
+sub setmax_unshared {
+    $MAX_UNSHARED_SIZE = shift;
+    my $r = Apache->request();
+    unless ($r->pnotes('size_limit_cleanup')) {
+        $r->connection->pool->cleanup_register(\&exit_if_too_big, $r);
+        $r->pnotes('size_limit_cleanup', 1);
+    }
+}
+
+sub handler {
+    my $r = shift;
+
+    if ($r->is_initial_req()) {
+        # we want to operate in a cleanup handler
+        if (ModPerl::Util::current_callback() eq 'PerlCleanupHandler') {
+            exit_if_too_big($r);
+        } else {
+            $r->connection->pool->cleanup_register(\&exit_if_too_big);
+        }
+    }
+
+    return Apache::DECLINED;
+}
+
+sub error_log {
+    print STDERR "[", scalar(localtime time),
+        "] ($$) Apache::SizeLimit @_\n";
+}
+
+1;
+

Reply via email to