operations, generally more robust handling of rows/offset attrs
- Fix incorrect $storage state on unexpected RDBMS disconnects and
other failure events, preventing clean reconnection (RT#110429)
+ - Ensure leaving an exception stack via Return::MultiLevel or something
+ similar produces a large warning
- Make sure exception objects stringifying to '' are properly handled
and warned about (GH#15)
- Fix corner case of stringify-only overloaded objects being used in
use DBIx::Class::Carp;
use Try::Tiny;
use Scalar::Util qw/weaken blessed/;
-use DBIx::Class::_Util qw(refcount quote_sub is_exception);
+use DBIx::Class::_Util qw(refcount quote_sub is_exception scope_guard);
use Devel::GlobalDestruction;
use namespace::clean;
my ($self, @args) = @_;
if (my $act = $self->exception_action) {
- try {
- # if it throws - good, we'll go down to the catch
+
+ my $guard_disarmed;
+
+ my $guard = scope_guard {
+ return if $guard_disarmed;
+ local $SIG{__WARN__};
+ Carp::cluck("
+ !!! DBIx::Class INTERNAL PANIC !!!
+
+The exception_action() handler installed on '$self'
+aborted the stacktrace below via a longjmp (either via Return::Multilevel or
+plain goto, or Scope::Upper or something equally nefarious). There currently
+is nothing safe DBIx::Class can do, aside from displaying this error. A future
+version ( 0.082900, when available ) will reduce the cases in which the
+handler is invoked, but this is neither a complete solution, nor can it do
+anything for other software that might be affected by a similar problem.
+
+ !!! FIX YOUR ERROR HANDLING !!!
+
+This guard was activated beginning"
+ );
+ };
+
+ eval {
+ # if it throws - good, we'll go down to the do{} below
# if it doesn't - do different things depending on RV truthiness
if( $act->(@args) ) {
$args[0] = (
carp_unique (
"The exception_action handler installed on $self returned false instead"
.' of throwing an exception. This behavior has been deprecated, adjust your'
- .' handler to always rethrow the supplied error.'
+ .' handler to always rethrow the supplied error'
);
}
- } catch {
+
+ $guard_disarmed = 1;
+ }
+
+ or
+
+ do {
# We call this to get the necessary warnings emitted and disregard the RV
- # as it's definitely an exception if we got as far as catch{}
- is_exception($_);
+ # as it's definitely an exception if we got as far as this do{} block
+ is_exception($@);
- die $_;
+ $guard_disarmed = 1;
+ $args[0] = $@;
};
}
--- /dev/null
+use strict;
+use warnings;
+
+use lib 't/lib';
+use DBICTest::RunMode;
+BEGIN {
+ if( DBICTest::RunMode->is_plain ) {
+ print "1..0 # SKIP not running dangerous segfault-prone test on plain install\n";
+ exit 0;
+ }
+}
+
+use File::Temp ();
+use DBIx::Class::_Util 'scope_guard';
+use DBIx::Class::Schema;
+
+# Do not use T::B - the test is hard enough not to segfault as it is
+my $test_count = 0;
+
+# start with one failure, and decrement it at the end
+my $failed = 1;
+
+sub ok {
+ printf STDOUT ("%s %u - %s\n",
+ ( $_[0] ? 'ok' : 'not ok' ),
+ ++$test_count,
+ $_[1] || '',
+ );
+
+ unless( $_[0] ) {
+ $failed++;
+ printf STDERR ("# Failed test #%d at %s line %d\n",
+ $test_count,
+ (caller(0))[1,2]
+ );
+ }
+
+ return !!$_[0];
+}
+
+# yes, make it even dirtier
+my $schema = 'DBIx::Class::Schema';
+
+$schema->connection('dbi:SQLite::memory:');
+
+# this is incredibly horrible...
+# demonstrate utter breakage of the reconnection/retry logic
+#
+open(my $stderr_copy, '>&', *STDERR) or die "Unable to dup STDERR: $!";
+my $tf = File::Temp->new( UNLINK => 1 );
+
+my $output;
+
+ESCAPE:
+{
+ my $guard = scope_guard {
+ close STDERR;
+ open(STDERR, '>&', $stderr_copy);
+ $output = do { local (@ARGV, $/) = $tf; <> };
+ close $tf;
+ unlink $tf;
+ undef $tf;
+ close $stderr_copy;
+ };
+
+ close STDERR;
+ open(STDERR, '>&', $tf) or die "Unable to reopen STDERR: $!";
+
+ $schema->storage->ensure_connected;
+ $schema->storage->_dbh->disconnect;
+
+ local $SIG{__WARN__} = sub {};
+
+ $schema->exception_action(sub {
+ ok(1, 'exception_action invoked');
+ # essentially what Dancer2's redirect() does after https://github.com/PerlDancer/Dancer2/pull/485
+ # which "nicely" combines with: https://metacpan.org/source/MARKOV/Log-Report-1.12/lib/Dancer2/Plugin/LogReport.pm#L143
+ # as encouraged by: https://metacpan.org/pod/release/MARKOV/Log-Report-1.12/lib/Dancer2/Plugin/LogReport.pod#Logging-DBIC-database-queries-and-errors
+ last ESCAPE;
+ });
+
+ # this *DOES* throw, but the exception will *NEVER SHOW UP*
+ $schema->storage->dbh_do(sub { $_[1]->selectall_arrayref("SELECT * FROM wfwqfdqefqef") } );
+
+ # NEITHER will this
+ ok(0, "Nope");
+}
+
+ok(1, "Post-escape reached");
+
+ok(
+ !!( $output =~ /DBIx::Class INTERNAL PANIC.+FIX YOUR ERROR HANDLING/s ),
+ 'Proper warning emitted on STDERR'
+) or print STDERR "Instead found:\n\n$output\n";
+
+print "1..$test_count\n";
+
+# this is our "done_testing"
+$failed--;
+
+# avoid tasty segfaults on 5.8.x
+exit( $failed );