Relax overly aggressive exception-well-formedness checks from 84e4e006
[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;
3d56e026 6use Scalar::Util qw(weaken blessed refaddr);
b4ad8a74 7use DBIx::Class;
e1d9e578 8use DBIx::Class::_Util qw(is_exception detected_reinvoked_destructor);
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...
35cf7d1a 27 #
28 # Deliberately *NOT* using is_exception - if someone left a misbehaving
29 # antipattern value in $@, it's not our business to whine about it
30 if( defined $@ and length $@ ) {
6e102c8f 31 weaken(
d52c4a75 32 $guard->{existing_exception_ref} = (length ref $@) ? $@ : \$@
6e102c8f 33 );
f62c5724 34 }
35
1bc193ac 36 $storage->txn_begin;
1f870d5a 37
6e102c8f 38 weaken( $guard->{dbh} = $storage->_dbh );
f62c5724 39
40 bless $guard, ref $class || $class;
1f870d5a 41
7d216b10 42 $guard;
1bc193ac 43}
44
45sub commit {
46 my $self = shift;
47
f62c5724 48 $self->{storage}->throw_exception("Refusing to execute multiple commits on scope guard $self")
49 if $self->{inactivated};
50
0bec44d5 51 # FIXME - this assumption may be premature: a commit may fail and a rollback
52 # *still* be necessary. Currently I am not aware of such scenarious, but I
53 # also know the deferred constraint handling is *severely* undertested.
54 # Making the change of "fire txn and never come back to this" in order to
55 # address RT#107159, but this *MUST* be reevaluated later.
f62c5724 56 $self->{inactivated} = 1;
0bec44d5 57 $self->{storage}->txn_commit;
1bc193ac 58}
59
60sub DESTROY {
e1d9e578 61 return if &detected_reinvoked_destructor;
3d56e026 62
f62c5724 63 my $self = shift;
1bc193ac 64
f62c5724 65 return if $self->{inactivated};
3b7f3eac 66
f62c5724 67 # if our dbh is not ours anymore, the $dbh weakref will go undef
0d8817bc 68 $self->{storage}->_verify_pid unless DBIx::Class::_ENV_::BROKEN_FORK;
f62c5724 69 return unless $self->{dbh};
7d216b10 70
f62c5724 71 my $exception = $@ if (
841efcb3 72 is_exception $@
f62c5724 73 and
74 (
75 ! defined $self->{existing_exception_ref}
76 or
d52c4a75 77 refaddr( (length ref $@) ? $@ : \$@ ) != refaddr($self->{existing_exception_ref})
f62c5724 78 )
79 );
c6e27318 80
a778f387 81 {
82 local $@;
36099e8c 83
84 carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
f62c5724 85 unless defined $exception;
36099e8c 86
ed7ab0f4 87 my $rollback_exception;
7d216b10 88 # do minimal connectivity check due to weird shit like
89 # https://rt.cpan.org/Public/Bug/Display.html?id=62370
f62c5724 90 try { $self->{storage}->_seems_connected && $self->{storage}->txn_rollback }
ed7ab0f4 91 catch { $rollback_exception = shift };
c6e27318 92
90d7422f 93 if ( $rollback_exception and (
94 ! defined blessed $rollback_exception
95 or
96 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
97 ) ) {
1f870d5a 98 # append our text - THIS IS A TEMPORARY FIXUP!
99 # a real stackable exception object is in the works
100 if (ref $exception eq 'DBIx::Class::Exception') {
101 $exception->{msg} = "Transaction aborted: $exception->{msg} "
102 ."Rollback failed: ${rollback_exception}";
103 }
104 elsif ($exception) {
36099e8c 105 $exception = "Transaction aborted: ${exception} "
c6e27318 106 ."Rollback failed: ${rollback_exception}";
36099e8c 107 }
108 else {
109 carp (join ' ',
110 "********************* ROLLBACK FAILED!!! ********************",
111 "\nA rollback operation failed after the guard went out of scope.",
112 'This is potentially a disastrous situation, check your data for',
113 "consistency: $rollback_exception"
114 );
115 }
c6e27318 116 }
3b7f3eac 117 }
36099e8c 118
5815ffb0 119 $@ = $exception;
1bc193ac 120}
121
1221;
123
124__END__
125
126=head1 NAME
127
6936e902 128DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling
1bc193ac 129
130=head1 SYNOPSIS
131
132 sub foo {
133 my ($self, $schema) = @_;
134
135 my $guard = $schema->txn_scope_guard;
136
137 # Multiple database operations here
138
139 $guard->commit;
140 }
141
142=head1 DESCRIPTION
143
144An object that behaves much like L<Scope::Guard>, but hardcoded to do the
fd323bf1 145right thing with transactions in DBIx::Class.
1bc193ac 146
147=head1 METHODS
148
149=head2 new
150
6936e902 151Creating an instance of this class will start a new transaction (by
152implicitly calling L<DBIx::Class::Storage/txn_begin>. Expects a
1bc193ac 153L<DBIx::Class::Storage> object as its only argument.
154
155=head2 commit
156
157Commit the transaction, and stop guarding the scope. If this method is not
48580715 158called and this object goes out of scope (e.g. an exception is thrown) then
6936e902 159the transaction is rolled back, via L<DBIx::Class::Storage/txn_rollback>
1bc193ac 160
161=cut
162
163=head1 SEE ALSO
164
165L<DBIx::Class::Schema/txn_scope_guard>.
166
a2bd3796 167L<Scope::Guard> by chocolateboy (inspiration for this module)
1bc193ac 168
a2bd3796 169=head1 FURTHER QUESTIONS?
1bc193ac 170
a2bd3796 171Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
1bc193ac 172
a2bd3796 173=head1 COPYRIGHT AND LICENSE
1bc193ac 174
a2bd3796 175This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
176by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
177redistribute it and/or modify it under the same terms as the
178L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.