1 package DBIx::Class::Storage::TxnScopeGuard;
6 use Scalar::Util qw/weaken blessed refaddr/;
8 use DBIx::Class::Exception;
12 my ($guards_count, $compat_handler, $foreign_handler);
15 my ($class, $storage) = @_;
22 # we are starting with an already set $@ - in order for things to work we need to
23 # be able to recognize it upon destruction - store its weakref
24 # recording it before doing the txn_begin stuff
25 if (defined $@ and $@ ne '') {
26 $guard->{existing_exception_ref} = (ref $@ ne '') ? $@ : \$@;
27 weaken $guard->{existing_exception_ref};
32 $guard->{dbh} = $storage->_dbh;
35 bless $guard, ref $class || $class;
37 # install a callback carefully
38 if (DBIx::Class::_ENV_::INVISIBLE_DOLLAR_AT and !$guards_count) {
40 # if the thrown exception is a plain string, wrap it in our
42 # this is actually a pretty cool idea, may very well keep it
44 $compat_handler ||= bless(
46 $@ = (blessed($_[0]) or ref($_[0]))
48 : bless ( { msg => $_[0] }, 'DBIx::Class::Exception')
52 '__TxnScopeGuard__FIXUP__',
55 if ($foreign_handler = $SIG{__DIE__}) {
56 $SIG{__DIE__} = bless (
58 # we trust the foreign handler to do whatever it wants, all we do is set $@
59 eval { $compat_handler->(@_) };
60 $foreign_handler->(@_);
62 '__TxnScopeGuard__FIXUP__',
66 $SIG{__DIE__} = $compat_handler;
78 $self->{storage}->throw_exception("Refusing to execute multiple commits on scope guard $self")
79 if $self->{inactivated};
81 $self->{storage}->txn_commit;
82 $self->{inactivated} = 1;
90 # don't touch unless it's ours, and there are no more of us left
92 DBIx::Class::_ENV_::INVISIBLE_DOLLAR_AT
97 if (ref $SIG{__DIE__} eq '__TxnScopeGuard__FIXUP__') {
98 # restore what we saved
99 if ($foreign_handler) {
100 $SIG{__DIE__} = $foreign_handler;
103 delete $SIG{__DIE__};
107 # make sure we do not leak the foreign one in case it exists
108 undef $foreign_handler;
111 return if $self->{inactivated};
113 # if our dbh is not ours anymore, the $dbh weakref will go undef
114 $self->{storage}->_verify_pid;
115 return unless $self->{dbh};
117 my $exception = $@ if (
123 ! defined $self->{existing_exception_ref}
125 refaddr( ref $@ eq '' ? \$@ : $@ ) != refaddr($self->{existing_exception_ref})
132 carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
133 unless defined $exception;
135 my $rollback_exception;
136 # do minimal connectivity check due to weird shit like
137 # https://rt.cpan.org/Public/Bug/Display.html?id=62370
138 try { $self->{storage}->_seems_connected && $self->{storage}->txn_rollback }
139 catch { $rollback_exception = shift };
141 if ( $rollback_exception and (
142 ! defined blessed $rollback_exception
144 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
146 # append our text - THIS IS A TEMPORARY FIXUP!
147 # a real stackable exception object is in the works
148 if (ref $exception eq 'DBIx::Class::Exception') {
149 $exception->{msg} = "Transaction aborted: $exception->{msg} "
150 ."Rollback failed: ${rollback_exception}";
153 $exception = "Transaction aborted: ${exception} "
154 ."Rollback failed: ${rollback_exception}";
158 "********************* ROLLBACK FAILED!!! ********************",
159 "\nA rollback operation failed after the guard went out of scope.",
160 'This is potentially a disastrous situation, check your data for',
161 "consistency: $rollback_exception"
167 $@ = $exception unless DBIx::Class::_ENV_::INVISIBLE_DOLLAR_AT;
176 DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling
181 my ($self, $schema) = @_;
183 my $guard = $schema->txn_scope_guard;
185 # Multiple database operations here
192 An object that behaves much like L<Scope::Guard>, but hardcoded to do the
193 right thing with transactions in DBIx::Class.
199 Creating an instance of this class will start a new transaction (by
200 implicitly calling L<DBIx::Class::Storage/txn_begin>. Expects a
201 L<DBIx::Class::Storage> object as its only argument.
205 Commit the transaction, and stop guarding the scope. If this method is not
206 called and this object goes out of scope (e.g. an exception is thrown) then
207 the transaction is rolled back, via L<DBIx::Class::Storage/txn_rollback>
213 L<DBIx::Class::Schema/txn_scope_guard>.
219 Inspired by L<Scope::Guard> by chocolateboy.
221 This module is free software. It may be used, redistributed and/or modified
222 under the same terms as Perl itself.