Author: timbo
Date: Thu Jun 7 09:46:08 2007
New Revision: 9632
Modified:
dbi/trunk/Changes
dbi/trunk/DBI.pm
dbi/trunk/lib/DBI/Gofer/Execute.pm
dbi/trunk/lib/DBI/ProfileData.pm
dbi/trunk/t/05thrclone.t
dbi/trunk/t/86gofer_fail.t
Log:
Fixed execute_for_fetch() to not cache errstr values thanks to Bart Degryse.
Fixed t/86gofer_fail tests to be less likely to fail falsely.
Added non-random (deterministic) mode to DBI_GOFER_RANDOM mechanism.
Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes (original)
+++ dbi/trunk/Changes Thu Jun 7 09:46:08 2007
@@ -43,12 +43,16 @@
Fixed XS versions of select*_*() methods to call execute()
fetch() etc., with inner handle instead of outer.
+ Fixed execute_for_fetch() to not cache errstr values
+ thanks to Bart Degryse.
Fixed unused var compiler warning thanks to JDHEDDEN.
+ Fixed t/86gofer_fail tests to be less likely to fail falsely.
Corrected timeout example in docs thanks to Egmont Koblinger.
Added support for !Time and !Time~N to DBI::Profile Path.
Added extra trace info to connect_cached thanks to Walery Studennikov.
+ Added non-random (deterministic) mode to DBI_GOFER_RANDOM mechanism.
DBI::ProfileDumper changes:
Don't write file if there's no profile data.
Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm (original)
+++ dbi/trunk/DBI.pm Thu Jun 7 09:46:08 2007
@@ -1921,7 +1921,7 @@
($tuple_status) ? @$tuple_status = () : $tuple_status = [];
my $rc_total = 0;
- my ($err_count, %errstr_cache);
+ my $err_count;
while ( my $tuple = &$fetch_tuple_sub() ) {
if ( my $rc = $sth->execute(@$tuple) ) {
push @$tuple_status, $rc;
@@ -1929,10 +1929,9 @@
}
else {
$err_count++;
- my $err = $sth->err;
- push @$tuple_status, [ $err, $errstr_cache{$err} ||=
$sth->errstr, $sth->state ];
+ push @$tuple_status, [ $sth->err, $sth->errstr, $sth->state ];
# XXX drivers implementing execute_for_fetch could opt to
"last;" here
- # if the know the error code means no further executes will
work.
+ # if they know the error code means no further executes will
work.
}
}
my $tuples = @$tuple_status;
Modified: dbi/trunk/lib/DBI/Gofer/Execute.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Execute.pm (original)
+++ dbi/trunk/lib/DBI/Gofer/Execute.pm Thu Jun 7 09:46:08 2007
@@ -563,11 +563,11 @@
my ($fail_percent, $delay_percent, $delay_duration);
my @specs = split /,/, $dbi_gofer_random;
for my $spec (@specs) {
- if ($spec =~ m/^fail=([.\d]+)%?$/) {
+ if ($spec =~ m/^fail=(-?[.\d]+)%?$/) {
$fail_percent = $1;
next;
}
- if ($spec =~ m/^delay([.\d]+)=([.\d]+)%?$/) {
+ if ($spec =~ m/^delay([.\d]+)=(-?[.\d]+)%?$/) {
$delay_duration = $1;
$delay_percent = $2;
next;
@@ -592,20 +592,30 @@
$dbh->{private_gofer_rand_fail_callbacks} = $callbacks;
}
+my %_mk_rand_callback_seqn;
sub _mk_rand_callback {
my ($self, $method, $fail_percent, $delay_percent, $delay_duration) = @_;
+ $fail_percent ||= 0; my $fail_modrate = int(1/(-$fail_percent )*100) if
$fail_percent;
+ $delay_percent ||= 0; my $delay_modrate = int(1/(-$delay_percent)*100) if
$delay_percent;
# note that $method may be "*"
return sub {
my ($h) = @_;
- if ($delay_percent && rand(100) < $delay_percent) {
+ my $seqn = ++$_mk_rand_callback_seqn{$method};
+ my $delay = ($delay_percent > 0) ? rand(100) < $delay_percent :
+ ($delay_percent < 0) ? !($seqn % $delay_modrate): 0;
+ my $fail = ($fail_percent > 0) ? rand(100) < $fail_percent :
+ ($fail_percent < 0) ? !($seqn % $fail_modrate) : 0;
+ #no warnings 'uninitialized';
+ #warn "_mk_rand_callback($fail_percent:$fail_modrate,
$delay_percent:$delay_modrate): seqn=$seqn fail=$fail delay=$delay";
+ if ($delay) {
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) {
+ if ($fail) {
undef $_; # tell DBI to not call the method
return $h->set_err(1, "fake error induced by DBI_GOFER_RANDOM env
var");
}
@@ -767,13 +777,15 @@
=item fail=R%
-Set the current random failure rate to R where R is a percentage. The value R
can be floating point, e.g., C<fail=0.05%>.
+Set the current failure rate to R where R is a percentage.
+The value R can be floating point, e.g., C<fail=0.05%>.
+Negative values for R have special meaning, see below.
=item delayN=R%
Set the current random delay rate to R where R is a percentage, and set the
current delay duration to N seconds. The values of R and N can be floating
point,
-e.g., C<delay120=0.1%>.
+e.g., C<delay120=0.1%>. Negative values for R have special meaning, see below.
=item methodname
@@ -791,6 +803,11 @@
will cause the do() method to fail for 0.01% of calls, and the execute()
method to
fail 0.01% of calls and be delayed by 60 seconds on 1% of calls.
+If the percentage value (C<R>) is negative then instead of the failures being
+triggered randomly (via the rand() function) they are triggered via a sequence
+number. In other words "C<fail=-20%>" will mean every fifth call will fail.
+Each method has a distinct sequence number.
+
=head1 AUTHOR
Tim Bunce, L<http://www.linkedin.com/in/timbunce>
Modified: dbi/trunk/lib/DBI/ProfileData.pm
==============================================================================
--- dbi/trunk/lib/DBI/ProfileData.pm (original)
+++ dbi/trunk/lib/DBI/ProfileData.pm Thu Jun 7 09:46:08 2007
@@ -192,7 +192,6 @@
# will clobber an existing $newfilename
rename($filename, $newfilename)
or croak "Can't rename($filename, $newfilename): $!";
- warn 42;
$filename = $newfilename;
}
Modified: dbi/trunk/t/05thrclone.t
==============================================================================
--- dbi/trunk/t/05thrclone.t (original)
+++ dbi/trunk/t/05thrclone.t Thu Jun 7 09:46:08 2007
@@ -24,8 +24,9 @@
# Something about DBD::Gofer causes a problem. Older versions didn't leak. It
# started at some point in development but I didn't track it down at the time
# so the exact change that made it start is now lost in the mists of time.
+# XXX doesn't seem to be happening any more
warn " You can ignore the $threads 'Scalars leaked' messages you may see here
(or send me a patch to fix the underlying problem)\n"
- if $ENV{DBI_AUTOPROXY} && not $ENV{DBI_PUREPERL};
+ if 0 && $ENV{DBI_AUTOPROXY} && not $ENV{DBI_PUREPERL};
{
package threads_sub;
Modified: dbi/trunk/t/86gofer_fail.t
==============================================================================
--- dbi/trunk/t/86gofer_fail.t (original)
+++ dbi/trunk/t/86gofer_fail.t Thu Jun 7 09:46:08 2007
@@ -28,6 +28,7 @@
plan 'no_plan';
my $tmp;
+my $dbh;
my $fails;
# we'll use the null transport for simplicity and speed
@@ -39,45 +40,43 @@
# --- 100% failure rate
-$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,
-});
-ok $dbh_100;
-
-ok !eval { $dbh_100->do("set foo=1") }, 'do method should fail';
-ok $dbh_100->errstr, 'errstr should be set';
-ok $@, '$@ should be set';
+($fails, $dbh) = trial_impact("fail=100%,do", 10, "", sub { $_->do("set
foo=1") });
+is $fails, 100, 'should fail 100% of the time';
+ok $@, '$@ should be set';
like $@, '/fake error induced by DBI_GOFER_RANDOM/';
-like $dbh_100->errstr, '/DBI_GOFER_RANDOM/', 'errstr should contain
DBI_GOFER_RANDOM';
+ok $dbh->errstr, 'errstr should be set';
+like $dbh->errstr, '/DBI_GOFER_RANDOM/', 'errstr should contain
DBI_GOFER_RANDOM';
+ok !$dbh->{go_response}->executed_flag_set, 'go_response executed flag should
be false';
-ok !$dbh_100->{go_response}->executed_flag_set, 'go_response executed flag
should be false';
-
-is precentage_exceptions(200, sub { $dbh_100->do("set foo=1") }), 100;
# XXX randomness can't be predicted, so it's just possible these will fail
+srand(42); # try to limit occasional failures (effect will vary by platform
etc)
+
+sub trial_impact {
+ my ($spec, $count, $dsn_attr, $code) = @_;
+ local $ENV{DBI_GOFER_RANDOM} = $spec;
+ my $dbh = dbi_connect("policy=rush;$dsn_attr");
+ local $_ = $dbh;
+ my $fail_percent = percentage_exceptions(200, $code);
+ return $fail_percent unless wantarray;
+ return ($fail_percent, $dbh);
+}
# --- 50% failure rate, with no retries
-$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") });
+$fails = trial_impact("fail=50%,do", 200, "retry_limit=0", sub { $_->do("set
foo=1") });
print "target approx 50% random failures, got $fails%\n";
between_ok $fails, 10, 90, "should fail about 50% of the time, but at least
between 10% and 90%";
# --- 50% failure rate, with many retries (should yield low failure rate)
-$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->prepare("set foo=1") });
-print "target approx 5% random failures, got $fails%\n";
+$fails = trial_impact("fail=50%,prepare", 200, "retry_limit=5", sub {
$_->prepare("set foo=1") });
+print "target less than 20% effective random failures (ideally 0), 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"; # 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") });
+$fails = trial_impact("fail=10,do", 200, "retry_limit=10", sub { $_->do("set
foo=1") });
cmp_ok $fails, '<', 1, 'should fail < 1%';
# --- 50% failure rate, test is_idempotent
@@ -89,35 +88,53 @@
go_retry_hook => sub { return ($_[0]->is_idempotent) ? 1 : 0 },
ReadOnly => 1,
} );
-between_ok precentage_exceptions(100, sub { $dbh_50r1ro->do("set foo=1") }),
+between_ok percentage_exceptions(100, sub { $dbh_50r1ro->do("set foo=1") }),
10, 40, 'should fail ~25% (ie 50% with one retry)';
between_ok $dbh_50r1ro->{go_transport}->meta->{request_retry_count},
- 35, 65, 'transport request_retry_count should be around 50';
+ 20, 80, 'transport request_retry_count should be around 50';
# test as above but with ReadOnly => 0
ok my $dbh_50r1rw = dbi_connect("policy=rush;retry_limit=1", {
go_retry_hook => sub { return ($_[0]->is_idempotent) ? 1 : 0 },
ReadOnly => 0,
} );
-between_ok precentage_exceptions(100, sub { $dbh_50r1rw->do("set foo=1") }),
- 30, 70, 'should fail ~50%, ie no retries';
+between_ok percentage_exceptions(100, sub { $dbh_50r1rw->do("set foo=1") }),
+ 20, 80, 'should fail ~50%, ie no retries';
ok !$dbh_50r1rw->{go_transport}->meta->{request_retry_count},
'transport request_retry_count should be zero or undef';
+# --- check random is random and non-random is non-random
+
+my %fail_percents;
+for (1..5) {
+ $fails = trial_impact("fail=50%,do", 10, "", sub { $_->do("set foo=1") });
+ ++$fail_percents{$fails};
+}
+cmp_ok scalar keys %fail_percents, '>=', 2, 'positive percentage should fail
randomly';
+
+%fail_percents = ();
+for (1..5) {
+ $fails = trial_impact("fail=-50%,do", 10, "", sub { $_->do("set foo=1") });
+ ++$fail_percents{$fails};
+}
+is scalar keys %fail_percents, 1, 'negative percentage should fail
non-randomly';
+
# ---
print "Testing random delay\n";
$ENV{DBI_GOFER_RANDOM} = "delay0.1=51%,do"; # odd percentage to force warn()s
@warns = ();
-ok my $dbh = dbi_connect("policy=rush;retry_limit=0");
-is precentage_exceptions(20, sub { $dbh->do("set foo=1") }),
+ok $dbh = dbi_connect("policy=rush;retry_limit=0");
+is percentage_exceptions(20, 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, 19, 'should be delayed around 5 times';
+between_ok $delays, 1, 19, 'should be delayed around 5 times';
exit 0;
+# --- subs ---
+#
sub between_ok {
my ($got, $min, $max, $label) = @_;
local $Test::Builder::Level = 2;
@@ -132,7 +149,7 @@
});
}
-sub precentage_exceptions {
+sub percentage_exceptions {
my ($count, $sub) = @_;
my $i = $count;
my $exceptions = 0;