1 package # hide from pause until we figure it all out
2 DBIx::Class::Storage::BlockRunner;
4 use Sub::Quote 'quote_sub';
5 use DBIx::Class::Exception;
7 use Context::Preserve 'preserve_context';
8 use Scalar::Util qw/weaken blessed/;
15 DBIx::Class::Storage::BlockRunner - Try running a block of code until success with a configurable retry logic
33 # true - retry, false - rethrow, or you can throw your own (not catching)
34 has retry_handler => (
39 or DBIx::Class::Exception->throw('retry_handler must be a CODE reference')
48 or DBIx::Class::Exception->throw('run_code must be a CODE reference')
55 (ref $_[0]) eq 'ARRAY'
56 or DBIx::Class::Exception->throw('run_args must be an ARRAY reference')
58 default => quote_sub( '[]' ),
63 default => quote_sub( '$ENV{DBIC_STORAGE_RETRY_DEBUG}' ),
66 has max_retried_count => (
68 default => quote_sub( '20' ),
71 has retried_count => (
74 writer => '_set_retried_count',
75 clearer => '_reset_retried_count',
76 default => quote_sub(q{ 0 }),
78 trigger => quote_sub(q{
79 DBIx::Class::Exception->throw(sprintf (
80 'Exceeded max_retried_count amount of %d, latest exception: %s',
81 $_[0]->max_retried_count, $_[0]->last_exception
82 )) if $_[0]->max_retried_count < ($_[1]||0);
86 has exception_stack => (
89 clearer => '_reset_exception_stack',
90 default => quote_sub(q{ [] }),
94 sub last_exception { shift->exception_stack->[-1] }
99 DBIx::Class::Exception->throw('run() takes no arguments') if @_;
101 $self->_reset_exception_stack;
102 $self->_reset_retried_count;
103 my $storage = $self->storage;
105 return $self->run_code->( @{$self->run_args} )
106 if (! $self->wrap_txn and $storage->{_in_do_block});
108 local $storage->{_in_do_block} = 1 unless $storage->{_in_do_block};
113 # this is the actual recursing worker
115 # warnings here mean I did not anticipate some ueber-complex case
116 # fatal warnings are not warranted
122 # from this point on (defined $txn_init_depth) is an indicator for wrap_txn
123 # save a bit on method calls
124 my $txn_init_depth = $self->wrap_txn ? $self->storage->transaction_depth : undef;
129 weaken (my $weakself = $self);
131 return preserve_context {
133 if (defined $txn_init_depth) {
134 $weakself->storage->txn_begin;
137 $weakself->run_code->( @{$weakself->run_args} );
140 (); # important, affects @_ below
145 my $storage = $weakself->storage;
146 my $cur_depth = $storage->transaction_depth;
148 if (defined $txn_init_depth and $run_err eq '') {
149 my $delta_txn = (1 + $txn_init_depth) - $cur_depth;
152 # a rollback in a top-level txn_do is valid-ish (seen in the wild and our own tests)
154 'Unexpected reduction of transaction depth by %d after execution of '
155 . '%s, skipping txn_commit()',
158 ) unless $delta_txn == 1 and $cur_depth == 0;
161 $run_err = eval { $storage->txn_commit; 1 } ? '' : $@;
165 # something above threw an error (could be the begin, the code or the commit)
166 if ($run_err ne '') {
168 # attempt a rollback if we did begin in the first place
170 # some DBDs go crazy if there is nothing to roll back on, perform a soft-check
171 my $rollback_exception = $storage->_seems_connected
172 ? (! eval { $storage->txn_rollback; 1 }) ? $@ : ''
173 : 'lost connection to storage'
176 if ( $rollback_exception and (
177 ! defined blessed $rollback_exception
179 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
181 $run_err = "Transaction aborted: $run_err. Rollback failed: $rollback_exception";
185 push @{ $weakself->exception_stack }, $run_err;
187 # init depth of > 0 ( > 1 with AC) implies nesting - no retry attempt queries
188 $storage->throw_exception($run_err) if (
190 defined $txn_init_depth
192 # FIXME - we assume that $storage->{_dbh_autocommit} is there if
193 # txn_init_depth is there, but this is a DBI-ism
194 $txn_init_depth > ( $storage->{_dbh_autocommit} ? 0 : 1 )
195 ) or ! $weakself->retry_handler->($weakself)
198 $weakself->_set_retried_count($weakself->retried_count + 1);
200 # we got that far - let's retry
201 carp( sprintf 'Retrying %s (run %d) after caught exception: %s',
203 $weakself->retried_count + 1,
205 ) if $weakself->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 preceeding 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 $weakself->_run;
218 return wantarray ? @res : $res[0];
228 You may distribute this code under the same terms as Perl itself.