Author: timbo
Date: Tue May  8 08:39:27 2007
New Revision: 9526

Modified:
   dbi/trunk/Changes
   dbi/trunk/DBI.xs
   dbi/trunk/Makefile.PL
   dbi/trunk/lib/DBD/Gofer/Transport/Base.pm
   dbi/trunk/lib/DBI/Gofer/Execute.pm
   dbi/trunk/t/86gofer_fail.t

Log:
Fixed printf arg warnings thanks to JDHEDDEN.
Added docs to DBD::Gofer::Transport::Base
Reworked DBI_GOFER_RANDOM_FAIL - it's now DBI_GOFER_RANDOM and can trigger both 
delays and failures.
Added warning about utf8 locals possibly triggering test failures.


Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Tue May  8 08:39:27 2007
@@ -8,6 +8,8 @@
 
 Assorted TODO notes:
 
+XXX write up DBI_GOFER_RANDOM
+
 Policy principle:
     Designed to influence behaviour of unaltered programs
     ie go_* attributes take precidence over policy
@@ -39,7 +41,7 @@
 Add trace modules that just records the last N trace messages into an array
 and prepends them to any error message.
 
-=head2 Changes in DBI 1.55 (svn rev 9480),  1st May 2007
+=head2 Changes in DBI 1.55 (svn rev 9504),  4th May 2007
 
   Fixed set_err() so HandleSetErr hook is executed reliably, if set.
   Fixed accuracy of profiling when perl configured to use long doubles.

Modified: dbi/trunk/DBI.xs
==============================================================================
--- dbi/trunk/DBI.xs    (original)
+++ dbi/trunk/DBI.xs    Tue May  8 08:39:27 2007
@@ -2930,8 +2930,8 @@
        SV *code = SvRV(*hook_svp);
         I32 skip_dispatch = 0;
        if (trace_level)
-           PerlIO_printf(DBILOGFP, "%c   {{ %s callback %s being invoked (mark 
%d)\n",
-               (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0), mark);
+           PerlIO_printf(DBILOGFP, "%c   {{ %s callback %s being invoked\n",
+               (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0));
 
         /* we don't use ENTER,SAVETMPS & FREETMPS,LEAVE because we may need 
mortal
          * results to live long enough to be returned to our caller
@@ -2963,9 +2963,9 @@
         DEFSV = SvREFCNT_inc(orig_defsv);
 
        if (trace_level)
-           PerlIO_printf(DBILOGFP, "%c   }} %s callback %s returned%s (mark 
%d)\n",
+           PerlIO_printf(DBILOGFP, "%c   }} %s callback %s returned%s\n",
                (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0),
-                skip_dispatch ? ", actual method will not be called" : "", mark
+                skip_dispatch ? ", actual method will not be called" : ""
             );
         if (skip_dispatch) {    /* XXX experimental */
             int ix = outitems;

Modified: dbi/trunk/Makefile.PL
==============================================================================
--- dbi/trunk/Makefile.PL       (original)
+++ dbi/trunk/Makefile.PL       Tue May  8 08:39:27 2007
@@ -50,6 +50,16 @@
 }
 
 
+if (($ENV{LANG}||'') =~ m/utf-?8/i) {
+    print "\n";
+    print "*** Your LANG environment variable is set to '$ENV{LANG}'\a\n";
+    print "*** This is known to cause problems for some perl installations.\n";
+    print "*** If you get test failures, please try again with LANG unset.\n";
+    print "*** Please also email [EMAIL PROTECTED] and include the output of 
'perl -V'\n";
+    print "\a\n";
+    sleep 4;
+}
+
 if ($Config{useithreads}) {
     print "\n";
     print "*** You are using a perl configured with threading enabled.\a\n";
@@ -149,7 +159,7 @@
     $opts{DEFINE} .= ' -Wmissing-noreturn -Wno-unused-parameter' if 
$gccversion ge "3.0";
     if ($is_developer && $::opt_g) {
         $opts{DEFINE} .= ' -DPERL_GCC_PEDANTIC -ansi -pedantic' if $gccversion 
ge "3.0";
-        $opts{DEFINE} .= ' -Wdisabled-optimization'             if $gccversion 
ge "3.0";
+        $opts{DEFINE} .= ' -Wdisabled-optimization -Wformat'    if $gccversion 
ge "3.0";
         $opts{DEFINE} .= ' -Wmissing-prototypes';
     }
 }

Modified: dbi/trunk/lib/DBD/Gofer/Transport/Base.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer/Transport/Base.pm   (original)
+++ dbi/trunk/lib/DBD/Gofer/Transport/Base.pm   Tue May  8 08:39:27 2007
@@ -27,9 +27,15 @@
 ));
 
 
-
 sub _init_trace { $ENV{DBD_GOFER_TRACE} || 0 }
 
+
+sub new_response {
+    my $self = shift;
+    return DBI::Gofer::Response->new(@_);
+}
+
+
 sub transmit_request {
     my ($self, $request) = @_;
     my $to = $self->go_timeout;
@@ -51,7 +57,7 @@
         if ($@) {
             return $self->transport_timedout("transmit_request", $to)
                 if $@ eq "TIMEOUT\n";
-            return DBI::Gofer::Response->new({ err => 1, errstr => $@ });
+            return self->new_response({ err => 1, errstr => $@ });
         }
 
         return $response;
@@ -93,7 +99,7 @@
         if ($@) {
             return $self->transport_timedout("receive_response", $to)
                 if $@ eq "TIMEOUT\n";
-            return DBI::Gofer::Response->new({ err => 1, errstr => $@ });
+            return $self->new_response({ err => 1, errstr => $@ });
         }
         return $response;
     };
@@ -128,7 +134,7 @@
 
     if (not defined $retry) {
         my $errstr = $response->errstr || '';
-        $retry = 1 if $errstr =~ m/fake error induced by 
DBI_GOFER_RANDOM_FAIL/;
+        $retry = 1 if $errstr =~ m/fake error induced by DBI_GOFER_RANDOM/;
     }
 
     if (not defined $retry) {
@@ -158,7 +164,7 @@
 sub transport_timedout {
     my ($self, $method, $timeout) = @_;
     $timeout ||= $self->go_timeout;
-    return DBI::Gofer::Response->new({ err => 1, errstr => "DBD::Gofer $method 
timed-out after $timeout seconds" });
+    return $self->new_response({ err => 1, errstr => "DBD::Gofer $method 
timed-out after $timeout seconds" });
 }
 
 
@@ -168,6 +174,77 @@
 
 DBD::Gofer::Transport::Base - base class for DBD::Gofer client transports
 
+=head1 SYNOPSIS
+
+  my $remote_dsn = "..."
+  
DBI->connect("dbi:Gofer:transport=...;url=...;timeout=...;retry_limit=...;dsn=$remote_dsn",...)
+            
+or, enable by setting the DBI_AUTOPROXY environment variable:
+                
+  export DBI_AUTOPROXY='dbi:Gofer:transport=...;url=...'
+        
+which will force I<all> DBI connections to be made via that Gofer server.
+
+=head1 DESCRIPTION
+
+This is the base class for all DBD::Gofer client transports.
+
+=head1 ATTRIBUTES
+
+Gofer transport attributes can be specified either in the attributes parameter
+of the connect() method call, or in the DSN string. When used in the DSN
+string, attribute names don't have the C<go_> prefix.
+
+=head2 go_dsn
+
+The full DBI DSN that the Gofer server should connect to on your behalf.
+
+When used in the DSN it must be the last element in the DSN string.
+
+=head2 go_timeout
+
+A time limit for sending a request and receiving a response. Some drivers may
+implement sending and receiving as separate steps, in which case (currently)
+the timeout applies to each separately.
+
+If a request needs to be resent then the timeout is restarted for each sending
+of a request and receiving of a response.
+
+=head2 go_retry_limit
+
+The maximum number of times an request may be retried. The default is 2.
+
+=head2 go_retry_hook
+
+This subroutine reference is called, if defined, for each response received 
where $response->err is true.
+
+The subroutine is pass three parameters: the request object, the response 
object, and the transport object.
+
+If it returns an undefined value then the default retry behaviour is used. See 
L</RETRY ON ERROR> below.
+
+If it returns a defined but false value then the request is not resent.
+
+If it returns true value then the request is resent, so long as the number of 
retries does not exceed C<go_retry_limit>.
+
+=head1 RETRY ON ERROR
+
+The default retry on error behaviour is:
+
+ - Retry if the error was due to DBI_GOFER_RANDOM. See L<DBI::Gofer::Execute>.
+
+ - Retry if $request->is_idempotent returns true. See L<DBI::Gofer::Request>.
+
+A retry won't be allowed if the number of previous retries has reached 
C<go_retry_limit>.
+
+=head1 TRACING
+
+Tracing of gofer requests and reponses can be enabled by setting the
+C<DBD_GOFER_TRACE> environment variable. A value of 1 gives a reasonably
+compact summary o each request and response. A value of 2 or more gives a
+detailed, and voluminous, dump.
+
+The trace is written using DBI->trace_msg() and so is written to the default
+DBI trace output, which is usually STDERR.
 
 =head1 AUTHOR AND COPYRIGHT
 
@@ -179,7 +256,7 @@
 
 =head1 SEE ALSO
 
-L<DBD::Gofer>
+L<DBD::Gofer>, L<DBI::Gofer::Request>, L<DBI::Gofer::Response>, 
L<DBI::Gofer::Execute>.
 
 and some example transports:
 

Modified: dbi/trunk/lib/DBI/Gofer/Execute.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Execute.pm  (original)
+++ dbi/trunk/lib/DBI/Gofer/Execute.pm  Tue May  8 08:39:27 2007
@@ -185,10 +185,10 @@
 
     $dbh->{ShowErrorStatement} = 1 if $local_log;
 
-    # note that this affects previously cached handles because 
$ENV{DBI_GOFER_RANDOM_FAIL}
+    # note that this affects previously cached handles because 
$ENV{DBI_GOFER_RANDOM}
     # isn't included in the cache key. Could add a go_rand_fail=>... attribute.
-    $self->_install_rand_fail_callbacks($dbh, $ENV{DBI_GOFER_RANDOM_FAIL})
-        if $ENV{DBI_GOFER_RANDOM_FAIL};
+    $self->_install_rand_callbacks($dbh, $ENV{DBI_GOFER_RANDOM})
+        if $ENV{DBI_GOFER_RANDOM};
 
     my $CK = $dbh->{CachedKids};
     if ($CK && keys %$CK > $self->{max_cached_sth_per_dbh}) {
@@ -504,38 +504,66 @@
 }
 
 
-sub _install_rand_fail_callbacks {
-    my ($self, $dbh, $dbi_gofer_random_fail) = @_;
-    my ($rand_fail_pct, @rand_fail_methods) = split /,/, 
$dbi_gofer_random_fail;
-    @rand_fail_methods = qw(do prepare) if [EMAIL PROTECTED]; # only works for 
dbh methods
-    if ($rand_fail_pct) {
-        warn "DBI_GOFER_RANDOM_FAIL set to '$ENV{DBI_GOFER_RANDOM_FAIL}' "
-            ."so random failures will be generated! "
-            ."(approx $rand_fail_pct% of calls to methods: 
@rand_fail_methods)\n";
-        my $callbacks = $dbh->{Callbacks} || {};
-        my $prev      = $dbh->{private_gofer_rand_fail_callbacks} || {};
-        for my $method (@rand_fail_methods) {
-            if ($callbacks->{$method} && $callbacks->{$method} != 
$prev->{$method}) {
-                warn "Callback for $method method already installed so 
DBI_GOFER_RANDOM_FAIL callback not installed\n";
-                next;
-            }
-            $callbacks->{$method} = $self->_mk_rand_fail_sub($rand_fail_pct, 
$method);
+sub _install_rand_callbacks {
+    my ($self, $dbh, $dbi_gofer_random) = @_;
+
+    my $callbacks = $dbh->{Callbacks} || {};
+    my $prev      = $dbh->{private_gofer_rand_fail_callbacks} || {};
+
+    # return if we've already setup this handle with callbacks for these specs
+    return if (($prev->{_dbi_gofer_random_spec}||'') eq $dbi_gofer_random);
+    $prev->{_dbi_gofer_random_spec} = $dbi_gofer_random;
+
+    my ($fail_percent, $delay_percent, $delay_duration);
+    my @specs = split /,/, $dbi_gofer_random;
+    for my $spec (@specs) {
+        if ($spec =~ m/^fail=([.\d]+)%?$/) {
+            $fail_percent = $1;
+            next;
+        }
+        if ($spec =~ m/^delay([.\d]+)=([.\d]+)%?$/) {
+            $delay_duration = $1;
+            $delay_percent  = $2;
+            next;
+        }
+        elsif ($spec !~ m/^(\w+|\*)$/) {
+            warn "Ignored DBI_GOFER_RANDOM item '$spec' which isn't a config 
or a dbh method name";
+            next;
         }
-        $dbh->{Callbacks} = $callbacks;
-        $dbh->{private_gofer_rand_fail_callbacks} = $callbacks;
+        my $method = $spec;
+        if ($callbacks->{$method} && $callbacks->{$method} != 
$prev->{$method}) {
+            warn "Callback for $method method already installed so 
DBI_GOFER_RANDOM callback not installed\n";
+            next;
+        }
+        unless (defined $fail_percent or defined $delay_percent) {
+            warn "Ignored DBI_GOFER_RANDOM item '$spec' because not preceeded 
by 'fail=N' and/or 'delayN=N'";
+            next;
+        }
+        warn "DBI_GOFER_RANDOM enabled for $method() - random failures/delays 
will be generated!\n";
+        $callbacks->{$method} = $self->_mk_rand_callback($method, 
$fail_percent, $delay_percent, $delay_duration);
     }
+    $dbh->{Callbacks} = $callbacks;
+    $dbh->{private_gofer_rand_fail_callbacks} = $callbacks;
 }
 
-sub _mk_rand_fail_sub {
-    my ($self, $rand_fail_pct, $method) = @_;
-    my $rand_fail_ratio = $rand_fail_pct/100;
-    # $method may be "*"
+
+sub _mk_rand_callback {
+    my ($self, $method, $fail_percent, $delay_percent, $delay_duration) = @_;
+    # note that $method may be "*"
     return sub {
-        my $rand = rand();
-        #warn sprintf "DBI_GOFER_RANDOM_FAIL($rand_fail_pct) %f - %f\n", 
$rand, 1/$rand_fail_pct;
-        return if $rand >= $rand_fail_ratio;
-        undef $_; # tell DBI to not call the method
-        return $_[0]->set_err(1, "fake error induced by DBI_GOFER_RANDOM_FAIL 
env var");
+        my ($h) = @_;
+        if ($delay_percent && rand(100) < $delay_percent) {
+            my $msg = "DBI_GOFER_RANDOM delaying execution of $method by 
$delay_duration seconds\n";
+            # Note what's happening in a trace message. If the delay percent 
is an odd
+            # number then use warn() so it's sent back to the client
+            ($delay_percent % 2 == 0) ? $h->trace_msg($msg) : warn($msg);
+            select undef, undef, undef, $delay_duration; # allows floating 
point value
+        }
+        if ($fail_percent && rand(100) < $fail_percent) {
+            undef $_; # tell DBI to not call the method
+            return $h->set_err(1, "fake error induced by DBI_GOFER_RANDOM env 
var");
+        }
+        return;
     }
 }
 

Modified: dbi/trunk/t/86gofer_fail.t
==============================================================================
--- dbi/trunk/t/86gofer_fail.t  (original)
+++ dbi/trunk/t/86gofer_fail.t  Tue May  8 08:39:27 2007
@@ -10,7 +10,7 @@
 use Test::More;
 sub between_ok;
 
-# here we test the DBI_GOFER_RANDOM_FAIL mechanism
+# here we test the DBI_GOFER_RANDOM mechanism
 # and how gofer deals with failures
 
 plan skip_all => "requires Callbacks which are not supported with PurePerl" if 
$DBI::PurePerl;
@@ -33,12 +33,13 @@
 # we'll use the null transport for simplicity and speed
 # and the rush policy to limit the number of interactions with the gofer 
executor
 
-# silence the "DBI_GOFER_RANDOM_FAIL set ..." warning
-$SIG{__WARN__} = sub { warn "@_" unless "@_" =~ /^DBI_GOFER_RANDOM_FAIL set/ };
+# silence the "DBI_GOFER_RANDOM..." warnings
+my @warns;
+$SIG{__WARN__} = sub { ("@_" =~ /^DBI_GOFER_RANDOM/) ? push(@warns, @_) : warn 
@_; };
 
 # --- 100% failure rate
 
-$ENV{DBI_GOFER_RANDOM_FAIL} = "100,do"; # total failure (almost)
+$ENV{DBI_GOFER_RANDOM} = "fail=100%,do"; # total failure
 my $dbh_100 = 
DBI->connect("dbi:Gofer:transport=null;policy=rush;dsn=dbi:ExampleP:", 0, 0, {
     RaiseError => 1, PrintError => 0,
 });
@@ -47,8 +48,8 @@
 ok !eval { $dbh_100->do("set foo=1") }, 'do method should fail';
 ok $dbh_100->errstr, 'errstr should be set';
 ok $@, '$@ should be set';
-like $@, '/fake error induced by DBI_GOFER_RANDOM_FAIL/';
-like $dbh_100->errstr, '/DBI_GOFER_RANDOM_FAIL/', 'errstr should contain 
DBI_GOFER_RANDOM_FAIL';
+like $@, '/fake error induced by DBI_GOFER_RANDOM/';
+like $dbh_100->errstr, '/DBI_GOFER_RANDOM/', 'errstr should contain 
DBI_GOFER_RANDOM';
 
 ok !$dbh_100->{go_response}->executed_flag_set, 'go_response executed flag 
should be false';
 
@@ -58,7 +59,7 @@
 
 # --- 50% failure rate, with no retries
 
-$ENV{DBI_GOFER_RANDOM_FAIL} = "50,do"; # 50% failure (almost)
+$ENV{DBI_GOFER_RANDOM} = "fail=50%,do"; # 50% failure (almost)
 ok my $dbh_50r0 = dbi_connect("policy=rush;retry_limit=0");
 $fails = precentage_exceptions(200, sub { $dbh_50r0->do("set foo=1") });
 print "target approx 50% random failures, got $fails%\n";
@@ -66,22 +67,22 @@
 
 # --- 50% failure rate, with many retries (should yield low failure rate)
 
-$ENV{DBI_GOFER_RANDOM_FAIL} = "50,do"; # 50% failure (almost)
+$ENV{DBI_GOFER_RANDOM} = "fail=50%,prepare"; # 50% failure (almost)
 ok my $dbh_50r5 = dbi_connect("policy=rush;retry_limit=5");
-$fails = precentage_exceptions(200, sub { $dbh_50r5->do("set foo=1") });
+$fails = precentage_exceptions(200, sub { $dbh_50r5->prepare("set foo=1") });
 print "target approx 5% random failures, got $fails%\n";
 cmp_ok $fails, '<', 20, 'should fail < 20%';
 
 # --- 10% failure rate, with many retries (should yield zero failure rate)
 
-$ENV{DBI_GOFER_RANDOM_FAIL} = "10,do";
+$ENV{DBI_GOFER_RANDOM} = "fail=10,do"; # without the % this time
 ok my $dbh_1r10 = dbi_connect("policy=rush;retry_limit=10");
 $fails = precentage_exceptions(200, sub { $dbh_1r10->do("set foo=1") });
 cmp_ok $fails, '<', 1, 'should fail < 1%';
 
 # --- 50% failure rate, test is_idempotent
 
-$ENV{DBI_GOFER_RANDOM_FAIL} = "50,do";   # 50%
+$ENV{DBI_GOFER_RANDOM} = "fail=50%,do";   # 50%
 
 # test go_retry_hook and that ReadOnly => 1 retries a non-idempotent statement
 ok my $dbh_50r1ro = dbi_connect("policy=rush;retry_limit=1", {
@@ -104,7 +105,16 @@
     'transport request_retry_count should be zero or undef';
 
 
+# ---
+print "Testing random delay\n";
 
+$ENV{DBI_GOFER_RANDOM} = "delay0.1=51%,do"; # odd percentage to force warn()s
[EMAIL PROTECTED] = ();
+ok my $dbh = dbi_connect("policy=rush;retry_limit=0");
+is precentage_exceptions(10, sub { $dbh->do("set foo=1") }),
+    0, "should not fail for DBI_GOFER_RANDOM='$ENV{DBI_GOFER_RANDOM}'";
+my $delays = grep { m/delaying execution/ } @warns;
+between_ok $delays, 2, 9, 'should be delayed around 5 times';
 
 exit 0;
 
@@ -129,7 +139,7 @@
     while ($i--) {
         eval { $sub->() };
         if ($@) {
-            die "Unexpected failure: $@" unless $@ =~ /DBI_GOFER_RANDOM_FAIL/;
+            die "Unexpected failure: $@" unless $@ =~ /DBI_GOFER_RANDOM/;
             ++$exceptions;
         }
     }

Reply via email to