You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@perl.apache.org by Patrick Mulvany <pa...@firedrake.org> on 2003/05/20 16:15:52 UTC

[rfc] Apache::DBI - Limiting cached handles

I have recently had issues related to Apache::DBI caching rarely used connection strings. This is not really a bug but rather a feature. However when working in a production enviroment this means that a minor oversight can result in a number of rarely used connections from each apache process being created. These are never cleaned up until the process finally dies.

I propose an extention to the existing API that adds two new functions :-

        Apache::DBI->setCacheTimeOut($timeout)

       This configures the usage of the cache timeout method, to timeout con-
       nections after a set length of time in seconds. Setting the timeout to
       0 (Default) will disable this option. This can be used to force a
       reconnect for drivers, which do not implement the ping-method.

        Apache::DBI->setCacheMax($max)

       This configures the usage of the cache max connections method, to limit
       the number of cached connections. Setting the max to 0 (Default) will
       disable this option. This can be used to stop rouge connection strings
       from being perminently cached.

I have included a patch that has been tested under my development systems (Apache 1.3.xx) but needs to be confirmed as viable against Apache 2.

Hope this is of interested and I would welcome some comment on this.

Paddy



Index: DBI/DBI.pm
===================================================================
RCS file: /cvs/public/Apache/DBI/DBI.pm,v
retrieving revision 1.7
diff -u -r1.7 DBI.pm
--- DBI/DBI.pm	4 Apr 2003 10:58:53 -0000	1.7
+++ DBI/DBI.pm	20 May 2003 13:30:23 -0000
@@ -23,6 +23,12 @@
 my %LastPingTime; # keeps track of last ping per data_source
 my $Idx;          # key of %Connected and %Rollback.
 
+my %DisconnectTime;    # keeps track of handle connect time
+my @DisconnectIdx;     # keeps track of disconnect order time
+my %ConnectCount;      # keeps track of number of uses of a handle
+my $CacheTimeOut = 0 ; # max time a connection can be cached for 
+my $CacheMax     = 0 ; # max connection cached
+
 
 # supposed to be called in a startup script.
 # stores the data_source of all connections, which are supposed to be created upon
@@ -55,6 +61,33 @@
     }
 }
 
+# supposed to be called in a startup script.
+# stores the maximinium length that any handle will be 
+# cached before being automatically disconnected.
+# 0 = Ignore
+
+sub setCacheTimeOut { 
+    my $class       = shift;
+    my $timeout     = shift;
+    # sanity check
+    if ($timeout =~ /\d+/) {
+        $CacheTimeOut = $timeout;
+    }
+}
+
+# supposed to be called in a startup script.
+# stores the maximinium number of handles that will be 
+# cached before the oldest is automatically disconnected.
+# 0 = Ignore
+
+sub setCacheMax { 
+    my $class       = shift;
+    my $max         = shift;
+    # sanity check
+    if ($max =~ /\d+/) {
+        $CacheMax = $max;
+    }
+}
 
 # the connect method called from DBI::connect
 
@@ -110,19 +143,60 @@
     # using the ping-method. Use eval for checking the connection 
     # handle in order to avoid problems (dying inside ping) when 
     # RaiseError being on and the handle is invalid.
-    if ($Connected{$Idx} and (!$needping or eval{$Connected{$Idx}->ping})) {
-        print STDERR "$prefix already connected to '$Idx'\n" if $Apache::DBI::DEBUG > 1;
+    # If cache timeout enabled then the handle must be within
+    # it's active period.
+    if ($Connected{$Idx} and (!$needping or eval{$Connected{$Idx}->ping}) and (!$CacheTimeOut or $DisconnectTime{$Idx}>$now)) {
+        print STDERR "$prefix already connected to '$Idx' Total handles ".(scalar keys %Connected)."\n" if $Apache::DBI::DEBUG > 1;
         return (bless $Connected{$Idx}, 'Apache::DBI::db');
     }
 
+    # Clean up timed out handles and remove last used when needed
+    if ($CacheTimeOut and scalar @DisconnectIdx and ($DisconnectIdx[0]<$now 
+          or ($CacheMax and (scalar @DisconnectIdx)>=$CacheMax))) {
+        print STDERR "$prefix Checking for redundant handles\n" if ($Apache::DBI::DEBUG > 1);
+        my $break_out = 5; # Break out after max 5 old handles cleaned up
+        while (scalar @DisconnectIdx and ($DisconnectIdx[0]<$now or ($CacheMax and (scalar @DisconnectIdx)>=$CacheMax))
+                and $break_out) {
+            for my $handle (keys %Connected) {
+                if ($DisconnectTime{$handle}==$DisconnectIdx[0]) {
+                   if ($Apache::DBI::DEBUG > 1) {
+                      if ($DisconnectIdx[0]<$now) {
+                          print STDERR "$prefix Expired '$handle'\n" 
+                      } else {
+                          print STDERR "$prefix Released '$handle'\n" 
+                      }
+                   }
+                   delete $Connected{$handle};
+                   shift @DisconnectIdx;
+                   delete $DisconnectTime{$handle};
+                   last;
+                }
+            }
+            $break_out--;
+        }
+        print STDERR "$prefix Breakout at $break_out\n" if ($Apache::DBI::DEBUG > 1);
+    }
+
+    # Clean up broken handles
+    my $find_id=undef;
+    for my $id (0..$#DisconnectIdx) {
+       $find_id=$id if ($DisconnectIdx[$id]==$DisconnectTime{$Idx});
+    }
+    splice(@DisconnectIdx, $find_id,1) if (defined $find_id);
+    delete $Connected{$Idx};
+    delete $DisconnectTime{$Idx};
+
     # either there is no database handle-cached or it is not valid,
     # so get a new database-handle and store it in the cache
-    delete $Connected{$Idx};
     $Connected{$Idx} = $drh->connect(@args);
     return undef if !$Connected{$Idx};
 
+    $ConnectCount{$Idx} = 0;
+    $DisconnectTime{$Idx} = $now + $CacheTimeOut;
+    push @DisconnectIdx, $DisconnectTime{$Idx};
+
     # return the new database handle
-    print STDERR "$prefix new connect to '$Idx'\n" if $Apache::DBI::DEBUG;
+    print STDERR "$prefix new connect to '$Idx' as handle ".(scalar @DisconnectIdx)."\n" if $Apache::DBI::DEBUG;
     return (bless $Connected{$Idx}, 'Apache::DBI::db');
 }
 
@@ -340,6 +414,20 @@
 the validation of the database handle. This can be used for drivers, which 
 do not implement the ping-method. Setting the timeout > 0 will ping the 
 database only if the last access was more than timeout seconds before. 
+
+ Apache::DBI->setCacheTimeOut($timeout)
+
+This configures the usage of the cache timeout method, to timeout connections
+after a set length of time in seconds. Setting the timeout to 0 (Default) will 
+disable this option. This can be used to force a reconnect for drivers, which 
+do not implement the ping-method.
+
+ Apache::DBI->setCacheMax($max)
+
+This configures the usage of the cache max connections method, to limit the 
+number of cached connections. Setting the max to 0 (Default) will disable 
+this option. This can be used to stop rouge connection strings from being 
+perminently cached. 
 
 For the menu item 'DBI connections' you need to call Apache::Status BEFORE 
 Apache::DBI ! For an example of the configuration order see startup.pl.