Author: timbo
Date: Fri Apr 13 05:41:48 2007
New Revision: 9406

Modified:
   dbi/trunk/lib/DBI/Gofer/Execute.pm

Log:
Add forced_single_resultset, max_cached_sth_per_dbh and max_cached_dbh_per_drh 
to gofer executor config.
Rename check_connect to check_request.
Added docs.


Modified: dbi/trunk/lib/DBI/Gofer/Execute.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Execute.pm  (original)
+++ dbi/trunk/lib/DBI/Gofer/Execute.pm  Fri Apr 13 05:41:48 2007
@@ -30,11 +30,14 @@
 
 
 __PACKAGE__->mk_accessors(qw(
-    check_connect
+    check_request
     default_connect_dsn
     forced_connect_dsn
     default_connect_attributes
     forced_connect_attributes
+    forced_single_resultset
+    max_cached_dbh_per_drh
+    max_cached_sth_per_dbh
     track_recent
     stats
 )); 
@@ -44,6 +47,7 @@
     my ($self, $args) = @_;
     $args->{default_connect_attributes} ||= {};
     $args->{forced_connect_attributes}  ||= {};
+    $args->{max_cached_sth_per_dbh}     ||= 1000;
     $args->{stats} ||= {};
     return $self->SUPER::new($args);
 }
@@ -60,11 +64,9 @@
 );
 
 my %extra_attr = (
-    # what driver-specific attributes should be returned for the driver being 
used?
     # Only referenced if the driver doesn't support private_attribute_info 
method.
+    # what driver-specific attributes should be returned for the driver being 
used?
     # keyed by $dbh->{Driver}{Name}
-    # XXX for dbh attr only need to be returned on first access by client
-    # the client should then cache them. So need a way to indicate that.
     # XXX for sth should split into attr specific to resultsets (where 
NUM_OF_FIELDS > 0) and others
     # which would reduce processing/traffic for non-select statements
     mysql  => {
@@ -114,26 +116,26 @@
 sub _connect {
     my ($self, $request) = @_;
 
-    # just a quick hack for now
     my $stats = $self->{stats};
-    if (++$stats->{_requests_served} % 1000 == 0) { # XXX config
-        # discard CachedKids from time to time
+
+    # discard CachedKids from time to time
+    if (++$stats->{_requests_served} % 1000 == 0 # XXX config?
+        and my $max_cached_dbh_per_drh = $self->{max_cached_dbh_per_drh}
+    ) {
         my %drivers = DBI->installed_drivers();
         while ( my ($driver, $drh) = each %drivers ) {
-            next if $driver eq 'Gofer'; # ie transport=null when testing
             next unless my $CK = $drh->{CachedKids};
-            # XXX currently we discard all regardless
-            # because that avoids the need to also handle
-            # limiting the prepared statement cache
-            my $cached_dbh_count = keys %$CK;
-            #next unless $cached_dbh_count > 20; # XXX config
-
-            DBI->trace_msg("Clearing $cached_dbh_count cached dbh from 
$driver");
+            next unless keys %$CK > $max_cached_dbh_per_drh;
+            next if $driver eq 'Gofer'; # ie transport=null when testing
+            DBI->trace_msg(sprintf "Clearing %d cached dbh from $driver",
+                scalar keys %$CK, $self->{max_cached_dbh_per_drh});
             $_->{Active} && $_->disconnect for values %$CK;
             %$CK = ();
         }
     }
 
+    local $ENV{DBI_AUTOPROXY}; # limit the insanity
+
     my ($connect_method, $dsn, $username, $password, $attr) = @{ 
$request->dbh_connect_call };
     $connect_method ||= 'connect_cached';
 
@@ -141,16 +143,10 @@
     # (Could just do this on client-side and trust the client. DoS?)
     delete @{$attr}{qw(Profile InactiveDestroy HandleError HandleSetErr 
TraceLevel Taint TaintIn TaintOut)};
 
-    if (my $check_connect = $self->check_connect) {
-        $check_connect->($dsn, $attr, $connect_method, $request);
-    }
-
     $dsn = $self->forced_connect_dsn || $dsn || $self->default_connect_dsn
         or die "No forced_connect_dsn, requested dsn, or default_connect_dsn 
for request";
 
-    local $ENV{DBI_AUTOPROXY}; # limit the insanity
-
-    # XXX implement our own private connect_cached method?
+    # XXX implement our own private connect_cached method? (with rate-limited 
ping)
     my $dbh = DBI->$connect_method($dsn, undef, undef, {
 
         # the configured default attributes, if any
@@ -175,6 +171,12 @@
         dbi_go_execute_unique => __PACKAGE__."$$",
     });
     $dbh->{ShowErrorStatement} = 1 if $local_log;
+
+    my $CK = $dbh->{CachedKids};
+    if ($CK && keys %$CK > $self->{max_cached_sth_per_dbh}) {
+        %$CK = (); #  clear all statement handles
+    }
+
     #$dbh->trace(0);
     return $dbh;
 }
@@ -223,6 +225,11 @@
 
     my $response = eval {
 
+        if (my $check_request = $self->check_request) {
+            $request = $check_request->($request)
+                or die "check_request failed";
+        }
+
         my $version = $request->version || 0;
         die ref($request)." version $version is not supported"
             if $version < 0.009116 or $version >= 1;
@@ -411,6 +418,7 @@
             if (my $rows = $rs->{rowset}) {
                 $row_count += @$rows;
             }
+            last if $self->{forced_single_resultset};
         } while $sth->more_results
              || $sth->{syb_more_results};
 
@@ -470,9 +478,80 @@
 1;
 __END__
 
-TODO
+=head1 NAME
+
+DBI::Gofer::Execute - Executes Gofer requests and returns Gofer responses
+
+=head1 SYNOPSIS
+
+  $executor = DBI::Gofer::Execute->new( { ...config... });
+
+  $response = $executor->execute_request( $request );
+
+=head1 DESCRIPTION
+
+Accepts a DBI::Gofer::Request object, executes the requested DBI method calls,
+and returns a DBI::Gofer::Response object.
+
+Any error, including any internal 'fatal' errors are caught and converted into
+a DBI::Gofer::Response object.
+
+=head1 CONFIGURATION
+
+=head2 check_request
+
+If defined, it must be a reference to a subroutine that will 'check' the 
request.
+
+The subroutine can either return the original request object or die with a
+suitable error message (which will be turned into a Gofer response).
+
+It can also construct and return a new request that should be executed instead
+of the original request.
+
+=head2 forced_connect_dsn
+
+If set, this DSN is always used instead of the one in the request.
+
+=head2 default_connect_dsn
+
+If set, this DSN is used if C<forced_connect_dsn> is not set and the request 
does not contain a DSN.
+
+=head2 forced_connect_attributes
+
+A reference to a hash of connect() attributes. Individual attributes in
+C<forced_connect_attributes> will take precedence over corresponding attributes
+in the request.
+
+=head2 default_connect_attributes
+
+A reference to a hash of connect() attributes. Individual attributes in the
+request take precedence over corresponding attributes in 
C<default_connect_attributes>.
+
+=head2 max_cached_dbh_per_drh
+
+If set, the loaded drivers will be checked to ensure they don't have more than
+this number of cached connections. There is no default value. This limit is not
+enforced for every request.
+
+=head2 max_cached_sth_per_dbh
+
+If set, all the cached statement handles will be cleared once the number of
+cached statement handles rises above this limit. The default is 1000.
+
+=head2 forced_single_resultset
+
+If true, then only a single result set will be fetched and returned in the 
response.
+
+=head2 track_recent
+
+If set, specifies the number of recent requests and responses that (the
+transport) should keep for diagnostics. See L<DBI::Gofer::Transport::mod_perl>
+Note that this setting can significantly increase memory use.
+
+=head1 TO DO
 
-Pruning of cached dbh and sth
+Currently every 1000 requests all the cached dbh are disconnected cleared to 
avoid
+the connection and statement handle caches growing too large. A smarter system 
is needed.
 
 =head1 AUTHOR AND COPYRIGHT
 

Reply via email to