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