Ditch Carp::Clan for our own thing
[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
ed7ab0f4 125 if (defined $rollback_exception && $rollback_exception !~ /DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION/) {
1f870d5a 126 # append our text - THIS IS A TEMPORARY FIXUP!
127 # a real stackable exception object is in the works
128 if (ref $exception eq 'DBIx::Class::Exception') {
129 $exception->{msg} = "Transaction aborted: $exception->{msg} "
130 ."Rollback failed: ${rollback_exception}";
131 }
132 elsif ($exception) {
36099e8c 133 $exception = "Transaction aborted: ${exception} "
c6e27318 134 ."Rollback failed: ${rollback_exception}";
36099e8c 135 }
136 else {
137 carp (join ' ',
138 "********************* ROLLBACK FAILED!!! ********************",
139 "\nA rollback operation failed after the guard went out of scope.",
140 'This is potentially a disastrous situation, check your data for',
141 "consistency: $rollback_exception"
142 );
143 }
c6e27318 144 }
3b7f3eac 145 }
36099e8c 146
1f870d5a 147 $@ = $exception unless IS_BROKEN_PERL;
1bc193ac 148}
149
1501;
151
152__END__
153
154=head1 NAME
155
6936e902 156DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling
1bc193ac 157
158=head1 SYNOPSIS
159
160 sub foo {
161 my ($self, $schema) = @_;
162
163 my $guard = $schema->txn_scope_guard;
164
165 # Multiple database operations here
166
167 $guard->commit;
168 }
169
170=head1 DESCRIPTION
171
172An object that behaves much like L<Scope::Guard>, but hardcoded to do the
fd323bf1 173right thing with transactions in DBIx::Class.
1bc193ac 174
175=head1 METHODS
176
177=head2 new
178
6936e902 179Creating an instance of this class will start a new transaction (by
180implicitly calling L<DBIx::Class::Storage/txn_begin>. Expects a
1bc193ac 181L<DBIx::Class::Storage> object as its only argument.
182
183=head2 commit
184
185Commit the transaction, and stop guarding the scope. If this method is not
48580715 186called and this object goes out of scope (e.g. an exception is thrown) then
6936e902 187the transaction is rolled back, via L<DBIx::Class::Storage/txn_rollback>
1bc193ac 188
189=cut
190
191=head1 SEE ALSO
192
193L<DBIx::Class::Schema/txn_scope_guard>.
194
195=head1 AUTHOR
196
197Ash Berlin, 2008.
198
48580715 199Inspired by L<Scope::Guard> by chocolateboy.
1bc193ac 200
201This module is free software. It may be used, redistributed and/or modified
202under the same terms as Perl itself.
203
204=cut