Another overhaul of transaction/savepoint handling
[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;
1f870d5a 6use Scalar::Util qw/weaken blessed/;
7use DBIx::Class::Exception;
70c28808 8use DBIx::Class::Carp;
1bc193ac 9
1f870d5a 10# temporary until we fix the $@ issue in core
11# we also need a real appendable, stackable exception object
12# (coming soon)
13BEGIN {
e69b5335 14 if ($] >= 5.013001 and $] <= 5.013007) {
1f870d5a 15 *IS_BROKEN_PERL = sub () { 1 };
16 }
17 else {
e69b5335 18 *IS_BROKEN_PERL = sub () { 0 };
1f870d5a 19 }
20}
21
9c1700e3 22use namespace::clean;
23
1f870d5a 24my ($guards_count, $compat_handler, $foreign_handler);
25
1bc193ac 26sub new {
27 my ($class, $storage) = @_;
28
29 $storage->txn_begin;
7d216b10 30 my $guard = bless [ 0, $storage, $storage->_dbh ], ref $class || $class;
1f870d5a 31
32
33 # install a callback carefully
34 if (IS_BROKEN_PERL and !$guards_count) {
35
36 # if the thrown exception is a plain string, wrap it in our
37 # own exception class
38 # this is actually a pretty cool idea, may very well keep it
39 # after perl is fixed
40 $compat_handler ||= bless(
41 sub {
42 $@ = (blessed($_[0]) or ref($_[0]))
43 ? $_[0]
44 : bless ( { msg => $_[0] }, 'DBIx::Class::Exception')
45 ;
46 die;
47 },
48 '__TxnScopeGuard__FIXUP__',
49 );
50
51 if ($foreign_handler = $SIG{__DIE__}) {
52 $SIG{__DIE__} = bless (
53 sub {
54 # we trust the foreign handler to do whatever it wants, all we do is set $@
55 eval { $compat_handler->(@_) };
56 $foreign_handler->(@_);
57 },
58 '__TxnScopeGuard__FIXUP__',
59 );
60 }
61 else {
62 $SIG{__DIE__} = $compat_handler;
63 }
64 }
65
66 $guards_count++;
67
7d216b10 68 weaken ($guard->[2]);
69 $guard;
1bc193ac 70}
71
72sub commit {
73 my $self = shift;
74
75 $self->[1]->txn_commit;
76 $self->[0] = 1;
77}
78
79sub DESTROY {
80 my ($dismiss, $storage) = @{$_[0]};
81
1f870d5a 82 $guards_count--;
83
84 # don't touch unless it's ours, and there are no more of us left
85 if (
86 IS_BROKEN_PERL
87 and
88 !$guards_count
89 ) {
90
91 if (ref $SIG{__DIE__} eq '__TxnScopeGuard__FIXUP__') {
92 # restore what we saved
93 if ($foreign_handler) {
94 $SIG{__DIE__} = $foreign_handler;
95 }
96 else {
97 delete $SIG{__DIE__};
98 }
99 }
100
101 # make sure we do not leak the foreign one in case it exists
102 undef $foreign_handler;
103 }
104
3b7f3eac 105 return if $dismiss;
106
7d216b10 107 # if our dbh is not ours anymore, the weakref will go undef
ec6415a9 108 $storage->_verify_pid;
7d216b10 109 return unless $_[0]->[2];
110
3b7f3eac 111 my $exception = $@;
c6e27318 112
a778f387 113 {
114 local $@;
36099e8c 115
116 carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
117 unless $exception;
118
ed7ab0f4 119 my $rollback_exception;
7d216b10 120 # do minimal connectivity check due to weird shit like
121 # https://rt.cpan.org/Public/Bug/Display.html?id=62370
122 try { $storage->_seems_connected && $storage->txn_rollback }
ed7ab0f4 123 catch { $rollback_exception = shift };
c6e27318 124
90d7422f 125 if ( $rollback_exception and (
126 ! defined blessed $rollback_exception
127 or
128 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
129 ) ) {
1f870d5a 130 # append our text - THIS IS A TEMPORARY FIXUP!
131 # a real stackable exception object is in the works
132 if (ref $exception eq 'DBIx::Class::Exception') {
133 $exception->{msg} = "Transaction aborted: $exception->{msg} "
134 ."Rollback failed: ${rollback_exception}";
135 }
136 elsif ($exception) {
36099e8c 137 $exception = "Transaction aborted: ${exception} "
c6e27318 138 ."Rollback failed: ${rollback_exception}";
36099e8c 139 }
140 else {
141 carp (join ' ',
142 "********************* ROLLBACK FAILED!!! ********************",
143 "\nA rollback operation failed after the guard went out of scope.",
144 'This is potentially a disastrous situation, check your data for',
145 "consistency: $rollback_exception"
146 );
147 }
c6e27318 148 }
3b7f3eac 149 }
36099e8c 150
1f870d5a 151 $@ = $exception unless IS_BROKEN_PERL;
1bc193ac 152}
153
1541;
155
156__END__
157
158=head1 NAME
159
6936e902 160DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling
1bc193ac 161
162=head1 SYNOPSIS
163
164 sub foo {
165 my ($self, $schema) = @_;
166
167 my $guard = $schema->txn_scope_guard;
168
169 # Multiple database operations here
170
171 $guard->commit;
172 }
173
174=head1 DESCRIPTION
175
176An object that behaves much like L<Scope::Guard>, but hardcoded to do the
fd323bf1 177right thing with transactions in DBIx::Class.
1bc193ac 178
179=head1 METHODS
180
181=head2 new
182
6936e902 183Creating an instance of this class will start a new transaction (by
184implicitly calling L<DBIx::Class::Storage/txn_begin>. Expects a
1bc193ac 185L<DBIx::Class::Storage> object as its only argument.
186
187=head2 commit
188
189Commit the transaction, and stop guarding the scope. If this method is not
48580715 190called and this object goes out of scope (e.g. an exception is thrown) then
6936e902 191the transaction is rolled back, via L<DBIx::Class::Storage/txn_rollback>
1bc193ac 192
193=cut
194
195=head1 SEE ALSO
196
197L<DBIx::Class::Schema/txn_scope_guard>.
198
199=head1 AUTHOR
200
201Ash Berlin, 2008.
202
48580715 203Inspired by L<Scope::Guard> by chocolateboy.
1bc193ac 204
205This module is free software. It may be used, redistributed and/or modified
206under the same terms as Perl itself.
207
208=cut