1 package # hide from pause until we figure it all out
2 DBIx::Class::Storage::BlockRunner;
6 use DBIx::Class::Exception;
8 use Context::Preserve 'preserve_context';
9 use DBIx::Class::_Util 'is_exception';
10 use Scalar::Util qw(weaken blessed reftype);
13 # DO NOT edit away without talking to riba first, he will just put it back
15 local $ENV{PERL_STRICTURES_EXTRA} = 0;
16 require Moo; Moo->import;
17 require Sub::Quote; Sub::Quote->import('quote_sub');
19 use warnings NONFATAL => 'all';
24 DBIx::Class::Storage::BlockRunner - Try running a block of code until success with a configurable retry logic
42 # true - retry, false - rethrow, or you can throw your own (not catching)
43 has retry_handler => (
47 (Scalar::Util::reftype($_[0])||'') eq 'CODE'
48 or DBIx::Class::Exception->throw('retry_handler must be a CODE reference')
54 # use a sub - to be evaluated on the spot lazily
55 default => quote_sub( '$ENV{DBIC_STORAGE_RETRY_DEBUG}' ),
64 has failed_attempt_count => (
66 init_arg => undef, # ensures one can't pass the value in
67 writer => '_set_failed_attempt_count',
70 trigger => quote_sub(q{
71 $_[0]->throw_exception( sprintf (
72 'Reached max_attempts amount of %d, latest exception: %s',
73 $_[0]->max_attempts, $_[0]->last_exception
74 )) if $_[0]->max_attempts <= ($_[1]||0);
78 has exception_stack => (
81 clearer => '_reset_exception_stack',
82 default => quote_sub(q{ [] }),
86 sub last_exception { shift->exception_stack->[-1] }
88 sub throw_exception { shift->storage->throw_exception (@_) }
93 $self->_reset_exception_stack;
94 $self->_set_failed_attempt_count(0);
98 $self->throw_exception('run() requires a coderef to execute as its first argument')
99 if ( reftype($cref)||'' ) ne 'CODE';
101 my $storage = $self->storage;
103 return $cref->( @_ ) if (
104 $storage->{_in_do_block}
109 local $storage->{_in_do_block} = 1 unless $storage->{_in_do_block};
111 return $self->_run($cref, @_);
114 # this is the actual recursing worker
116 # internal method - we know that both refs are strong-held by the
117 # calling scope of run(), hence safe to weaken everything
118 weaken( my $self = shift );
119 weaken( my $cref = shift );
121 my $args = @_ ? \@_ : [];
123 # from this point on (defined $txn_init_depth) is an indicator for wrap_txn
124 # save a bit on method calls
125 my $txn_init_depth = $self->wrap_txn ? $self->storage->transaction_depth : undef;
130 return preserve_context {
132 if (defined $txn_init_depth) {
133 $self->storage->txn_begin;
139 (); # important, affects @_ below
144 my $storage = $self->storage;
145 my $cur_depth = $storage->transaction_depth;
147 if (defined $txn_init_depth and $run_err eq '') {
148 my $delta_txn = (1 + $txn_init_depth) - $cur_depth;
151 # a rollback in a top-level txn_do is valid-ish (seen in the wild and our own tests)
153 'Unexpected reduction of transaction depth by %d after execution of '
154 . '%s, skipping txn_commit()',
157 ) unless $delta_txn == 1 and $cur_depth == 0;
160 $run_err = eval { $storage->txn_commit; 1 } ? '' : $@;
164 # something above threw an error (could be the begin, the code or the commit)
165 if ( is_exception $run_err ) {
167 # attempt a rollback if we did begin in the first place
169 # some DBDs go crazy if there is nothing to roll back on, perform a soft-check
170 my $rollback_exception = $storage->_seems_connected
171 ? (! eval { $storage->txn_rollback; 1 }) ? $@ : ''
172 : 'lost connection to storage'
175 if ( $rollback_exception and (
176 ! defined blessed $rollback_exception
178 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
180 $run_err = "Transaction aborted: $run_err. Rollback failed: $rollback_exception";
184 push @{ $self->exception_stack }, $run_err;
186 # this will throw if max_attempts is reached
187 $self->_set_failed_attempt_count($self->failed_attempt_count + 1);
189 # init depth of > 0 ( > 1 with AC) implies nesting - no retry attempt queries
190 $storage->throw_exception($run_err) if (
192 defined $txn_init_depth
194 # FIXME - we assume that $storage->{_dbh_autocommit} is there if
195 # txn_init_depth is there, but this is a DBI-ism
196 $txn_init_depth > ( $storage->{_dbh_autocommit} ? 0 : 1 )
197 ) or ! $self->retry_handler->($self)
200 # we got that far - let's retry
201 carp( sprintf 'Retrying %s (attempt %d) after caught exception: %s',
203 $self->failed_attempt_count + 1,
205 ) if $self->retry_debug;
207 $storage->ensure_connected;
208 # if txn_depth is > 1 this means something was done to the
209 # original $dbh, otherwise we would not get past the preceding if()
210 $storage->throw_exception(sprintf
211 'Unexpected transaction depth of %d on freshly connected handle',
212 $storage->transaction_depth,
213 ) if (defined $txn_init_depth and $storage->transaction_depth);
215 return $self->_run($cref, @$args);
218 return wantarray ? @res : $res[0];
222 =head1 AUTHOR AND CONTRIBUTORS
224 See L<AUTHOR|DBIx::Class/AUTHOR> and L<CONTRIBUTORS|DBIx::Class/CONTRIBUTORS> in DBIx::Class
228 You may distribute this code under the same terms as Perl itself.