Improve error reporting when we encounter broken exception objects
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / TxnScopeGuard.pm
CommitLineData
6936e902 1package DBIx::Class::Storage::TxnScopeGuard;
1bc193ac 2
3use strict;
4use warnings;
ed7ab0f4 5use Try::Tiny;
f62c5724 6use Scalar::Util qw/weaken blessed refaddr/;
b4ad8a74 7use DBIx::Class;
841efcb3 8use DBIx::Class::_Util 'is_exception';
70c28808 9use DBIx::Class::Carp;
9c1700e3 10use namespace::clean;
11
1bc193ac 12sub new {
13 my ($class, $storage) = @_;
14
f62c5724 15 my $guard = {
16 inactivated => 0,
17 storage => $storage,
18 };
19
20 # we are starting with an already set $@ - in order for things to work we need to
21 # be able to recognize it upon destruction - store its weakref
22 # recording it before doing the txn_begin stuff
6e102c8f 23 #
24 # FIXME FRAGILE - any eval that fails but *does not* rethrow between here
25 # and the unwind will trample over $@ and invalidate the entire mechanism
26 # There got to be a saner way of doing this...
841efcb3 27 if (is_exception $@) {
6e102c8f 28 weaken(
841efcb3 29 $guard->{existing_exception_ref} = (ref($@) eq '') ? \$@ : $@
6e102c8f 30 );
f62c5724 31 }
32
1bc193ac 33 $storage->txn_begin;
1f870d5a 34
6e102c8f 35 weaken( $guard->{dbh} = $storage->_dbh );
f62c5724 36
37 bless $guard, ref $class || $class;
1f870d5a 38
7d216b10 39 $guard;
1bc193ac 40}
41
42sub commit {
43 my $self = shift;
44
f62c5724 45 $self->{storage}->throw_exception("Refusing to execute multiple commits on scope guard $self")
46 if $self->{inactivated};
47
48 $self->{storage}->txn_commit;
49 $self->{inactivated} = 1;
1bc193ac 50}
51
52sub DESTROY {
f62c5724 53 my $self = shift;
1bc193ac 54
f62c5724 55 return if $self->{inactivated};
3b7f3eac 56
f62c5724 57 # if our dbh is not ours anymore, the $dbh weakref will go undef
0d8817bc 58 $self->{storage}->_verify_pid unless DBIx::Class::_ENV_::BROKEN_FORK;
f62c5724 59 return unless $self->{dbh};
7d216b10 60
f62c5724 61 my $exception = $@ if (
841efcb3 62 is_exception $@
f62c5724 63 and
64 (
65 ! defined $self->{existing_exception_ref}
66 or
841efcb3 67 refaddr( ref($@) eq '' ? \$@ : $@ ) != refaddr($self->{existing_exception_ref})
f62c5724 68 )
69 );
c6e27318 70
a778f387 71 {
72 local $@;
36099e8c 73
74 carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
f62c5724 75 unless defined $exception;
36099e8c 76
ed7ab0f4 77 my $rollback_exception;
7d216b10 78 # do minimal connectivity check due to weird shit like
79 # https://rt.cpan.org/Public/Bug/Display.html?id=62370
f62c5724 80 try { $self->{storage}->_seems_connected && $self->{storage}->txn_rollback }
ed7ab0f4 81 catch { $rollback_exception = shift };
c6e27318 82
90d7422f 83 if ( $rollback_exception and (
84 ! defined blessed $rollback_exception
85 or
86 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
87 ) ) {
1f870d5a 88 # append our text - THIS IS A TEMPORARY FIXUP!
89 # a real stackable exception object is in the works
90 if (ref $exception eq 'DBIx::Class::Exception') {
91 $exception->{msg} = "Transaction aborted: $exception->{msg} "
92 ."Rollback failed: ${rollback_exception}";
93 }
94 elsif ($exception) {
36099e8c 95 $exception = "Transaction aborted: ${exception} "
c6e27318 96 ."Rollback failed: ${rollback_exception}";
36099e8c 97 }
98 else {
99 carp (join ' ',
100 "********************* ROLLBACK FAILED!!! ********************",
101 "\nA rollback operation failed after the guard went out of scope.",
102 'This is potentially a disastrous situation, check your data for',
103 "consistency: $rollback_exception"
104 );
105 }
c6e27318 106 }
3b7f3eac 107 }
36099e8c 108
5815ffb0 109 $@ = $exception;
1bc193ac 110}
111
1121;
113
114__END__
115
116=head1 NAME
117
6936e902 118DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling
1bc193ac 119
120=head1 SYNOPSIS
121
122 sub foo {
123 my ($self, $schema) = @_;
124
125 my $guard = $schema->txn_scope_guard;
126
127 # Multiple database operations here
128
129 $guard->commit;
130 }
131
132=head1 DESCRIPTION
133
134An object that behaves much like L<Scope::Guard>, but hardcoded to do the
fd323bf1 135right thing with transactions in DBIx::Class.
1bc193ac 136
137=head1 METHODS
138
139=head2 new
140
6936e902 141Creating an instance of this class will start a new transaction (by
142implicitly calling L<DBIx::Class::Storage/txn_begin>. Expects a
1bc193ac 143L<DBIx::Class::Storage> object as its only argument.
144
145=head2 commit
146
147Commit the transaction, and stop guarding the scope. If this method is not
48580715 148called and this object goes out of scope (e.g. an exception is thrown) then
6936e902 149the transaction is rolled back, via L<DBIx::Class::Storage/txn_rollback>
1bc193ac 150
151=cut
152
153=head1 SEE ALSO
154
155L<DBIx::Class::Schema/txn_scope_guard>.
156
157=head1 AUTHOR
158
159Ash Berlin, 2008.
160
48580715 161Inspired by L<Scope::Guard> by chocolateboy.
1bc193ac 162
163This module is free software. It may be used, redistributed and/or modified
164under the same terms as Perl itself.
165
166=cut