# Need a way to parameterize this for Carp::Skip
$1 !~ /^(?: DBIx::Class::Storage::BlockRunner | Context::Preserve | Try::Tiny | Class::Accessor::Grouped | Class::C3::Componentised | Module::Runtime | Sub::Uplevel )$/x
and
- $2 !~ /^(?: throw_exception | carp | carp_unique | carp_once | dbh_do | txn_do | with_deferred_fk_checks)$/x
+ $2 !~ /^(?: throw_exception | carp | carp_unique | carp_once | dbh_do | txn_do | with_deferred_fk_checks | __delicate_rollback )$/x
#############################
) ? $f[3] : undef;
}
}
+# to be called by several internal stacked transaction handler codepaths
+# not for external consumption
+# *DOES NOT* throw exceptions, instead:
+# - returns false on success
+# - returns the exception on failed rollback
+sub __delicate_rollback {
+ my $self = shift;
+
+ if (
+ $self->transaction_depth > 1
+ and
+ # FIXME - the autosvp check here shouldn't be happening, it should be a role-ish thing
+ # The entire concept needs to be rethought with the storage layer... or something
+ ! $self->auto_savepoint
+ and
+ # the handle seems healthy, and there is nothing for us to do with it
+ # just go ahead and bow out, without triggering the txn_rollback() "nested exception"
+ # the unwind will eventually fail somewhere higher up if at all
+ # FIXME: a ::Storage::DBI-specific method, not a generic ::Storage one
+ $self->_seems_connected
+ ) {
+ # all above checks out - there is nothing to do on the $dbh itself
+ # just a plain soft-decrease of depth
+ $self->{transaction_depth}--;
+ return;
+ }
+
+ my $rbe;
+
+ local $@; # taking no chances
+ unless( eval { $self->txn_rollback; 1 } ) {
+
+ $rbe = $@;
+
+ # we were passed an existing exception to augment (think DESTROY stacks etc)
+ if (@_) {
+ my $exception = shift;
+
+ # append our text - THIS IS A TEMPORARY FIXUP!
+ #
+ # If the passed in exception is a reference, or an object we don't know
+ # how to augment - flattening it is just damn rude
+ if (
+ # FIXME - a better way, not liable to destroy an existing exception needs
+ # to be created. For the time being perpetuating the sin below in order
+ # to break the deadlock of which yak is being shaved first
+ 0
+ and
+ length ref $$exception
+ and
+ (
+ ! defined blessed $$exception
+ or
+ ! $$exception->isa( 'DBIx::Class::Exception' )
+ )
+ ) {
+
+ ##################
+ ### FIXME - TODO
+ ##################
+
+ }
+ else {
+
+ # SUCH HIDEOUS, MUCH AUGH! (and double WOW on the s/// at the end below)
+ $rbe =~ s/ at .+? line \d+$//;
+
+ (
+ (
+ defined blessed $$exception
+ and
+ $$exception->isa( 'DBIx::Class::Exception' )
+ )
+ ? (
+ $$exception->{msg} =
+ "Transaction aborted: $$exception->{msg}. Rollback failed: $rbe"
+ )
+ : (
+ $$exception =
+ "Transaction aborted: $$exception. Rollback failed: $rbe"
+ )
+ ) =~ s/Transaction aborted: (?=Transaction aborted:)//;
+ }
+ }
+ }
+
+ return $rbe;
+}
+
=head2 svp_begin
Arguments: $savepoint_name?
# something above threw an error (could be the begin, the code or the commit)
if ( is_exception $run_err ) {
- # attempt a rollback if we did begin in the first place
- if ($txn_begin_ok) {
- # some DBDs go crazy if there is nothing to roll back on, perform a soft-check
- my $rollback_exception = $storage->_seems_connected
- ? (! eval { $storage->txn_rollback; 1 }) ? $@ : ''
- : 'lost connection to storage'
- ;
-
- if ( $rollback_exception and (
- ! defined blessed $rollback_exception
- or
- ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
- ) ) {
- $run_err = "Transaction aborted: $run_err. Rollback failed: $rollback_exception";
- }
- }
+ # Attempt a rollback if we did begin in the first place
+ # Will append rollback error if possible
+ $storage->__delicate_rollback( \$run_err )
+ if $txn_begin_ok;
push @{ $self->exception_stack }, $run_err;
sub txn_rollback {
my $self = shift;
- $self->throw_exception("Unable to txn_rollback() on a disconnected storage")
+ # do a minimal connectivity check due to weird shit like
+ # https://rt.cpan.org/Public/Bug/Display.html?id=62370
+ $self->throw_exception("lost connection to storage")
unless $self->_seems_connected;
# esoteric case for folks using external $dbh handles
unimplemented => [qw/
_arm_global_destructor
_verify_pid
+ __delicate_rollback
get_use_dbms_capability
set_use_dbms_capability
return if $_[0]->{inactivated};
- # if our dbh is not ours anymore, the $dbh weakref will go undef
- $_[0]->{storage}->_verify_pid unless DBIx::Class::_ENV_::BROKEN_FORK;
- return unless $_[0]->{dbh};
+ # grab it before we've done volatile stuff below
my $current_exception = (
is_exception $@
and
: undef
;
- {
- local $@;
-
- carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
- unless defined $current_exception;
-
- my $rollback_exception;
- # do minimal connectivity check due to weird shit like
- # https://rt.cpan.org/Public/Bug/Display.html?id=62370
- eval {
- $_[0]->{storage}->_seems_connected && $_[0]->{storage}->txn_rollback;
- 1;
- } or $rollback_exception = $@;
-
- if ( $rollback_exception and (
- ! defined blessed $rollback_exception
- or
- ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
- ) ) {
- # append our text - THIS IS A TEMPORARY FIXUP!
- # a real stackable exception object is in the works
- if (ref $current_exception eq 'DBIx::Class::Exception') {
- $current_exception->{msg} = "Transaction aborted: $current_exception->{msg} "
- ."Rollback failed: ${rollback_exception}";
- }
- elsif ($current_exception) {
- $current_exception = "Transaction aborted: ${current_exception} "
- ."Rollback failed: ${rollback_exception}";
- }
- else {
- carp (join ' ',
- "********************* ROLLBACK FAILED!!! ********************",
- "\nA rollback operation failed after the guard went out of scope.",
- 'This is potentially a disastrous situation, check your data for',
- "consistency: $rollback_exception"
- );
- }
- }
+
+ # if our dbh is not ours anymore, the $dbh weakref will go undef
+ $_[0]->{storage}->_verify_pid unless DBIx::Class::_ENV_::BROKEN_FORK;
+ return unless defined $_[0]->{dbh};
+
+
+ carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
+ unless defined $current_exception;
+
+
+ if (
+ my $rollback_exception = $_[0]->{storage}->__delicate_rollback(
+ defined $current_exception
+ ? \$current_exception
+ : ()
+ )
+ and
+ ! defined $current_exception
+ ) {
+ carp (join ' ',
+ "********************* ROLLBACK FAILED!!! ********************",
+ "\nA rollback operation failed after the guard went out of scope.",
+ 'This is potentially a disastrous situation, check your data for',
+ "consistency: $rollback_exception"
+ );
}
- $@ = $current_exception;
+ $@ = $current_exception
+ if DBIx::Class::_ENV_::UNSTABLE_DOLLARAT;
}
1;
die $broken_exception
});
})
- } qr/\QTransaction aborted: $broken_exception. Rollback failed: lost connection to storage at @{[__FILE__]} line $ln\E\n/; # FIXME wtf - ...\E$/m doesn't work here
+ } qr/\QTransaction aborted: $broken_exception. Rollback failed: DBIx::Class::Storage::DBI::txn_rollback(): lost connection to storage at @{[__FILE__]} line $ln\E\n/; # FIXME wtf - ...\E$/m doesn't work here
is @w, 1, 'One matching warning only';