Protect DBIC as best we can from the failure mode in 7cb35852
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / BlockRunner.pm
CommitLineData
9345b14c 1package # hide from pause until we figure it all out
2 DBIx::Class::Storage::BlockRunner;
3
7f9a3f70 4use warnings;
59d624cf 5use strict;
6
cbd7f87a 7use DBIx::Class::Exception;
8use DBIx::Class::Carp;
9use Context::Preserve 'preserve_context';
ddcc02d1 10use DBIx::Class::_Util qw( is_exception qsub dbic_internal_try );
cbd7f87a 11use Scalar::Util qw(weaken blessed reftype);
12use Try::Tiny;
0020e364 13use Moo;
9345b14c 14use namespace::clean;
15
16=head1 NAME
17
18DBIx::Class::Storage::BlockRunner - Try running a block of code until success with a configurable retry logic
19
20=head1 DESCRIPTION
21
22=head1 METHODS
23
24=cut
25
26has storage => (
27 is => 'ro',
28 required => 1,
29);
30
31has wrap_txn => (
32 is => 'ro',
33 required => 1,
34);
35
36# true - retry, false - rethrow, or you can throw your own (not catching)
37has retry_handler => (
38 is => 'ro',
39 required => 1,
7f9a3f70 40 isa => qsub q{
7d534e68 41 (Scalar::Util::reftype($_[0])||'') eq 'CODE'
9345b14c 42 or DBIx::Class::Exception->throw('retry_handler must be a CODE reference')
7f9a3f70 43 },
9345b14c 44);
45
46has retry_debug => (
47 is => 'rw',
7d534e68 48 # use a sub - to be evaluated on the spot lazily
7f9a3f70 49 default => qsub '$ENV{DBIC_STORAGE_RETRY_DEBUG}',
7d534e68 50 lazy => 1,
9345b14c 51);
52
7d534e68 53has max_attempts => (
9345b14c 54 is => 'ro',
7d534e68 55 default => 20,
9345b14c 56);
57
7d534e68 58has failed_attempt_count => (
9345b14c 59 is => 'ro',
7d534e68 60 init_arg => undef, # ensures one can't pass the value in
61 writer => '_set_failed_attempt_count',
62 default => 0,
9345b14c 63 lazy => 1,
7f9a3f70 64 trigger => qsub q{
f9080e45 65 $_[0]->throw_exception( sprintf (
7d534e68 66 'Reached max_attempts amount of %d, latest exception: %s',
67 $_[0]->max_attempts, $_[0]->last_exception
68 )) if $_[0]->max_attempts <= ($_[1]||0);
7f9a3f70 69 },
9345b14c 70);
71
72has exception_stack => (
73 is => 'ro',
74 init_arg => undef,
75 clearer => '_reset_exception_stack',
7f9a3f70 76 default => qsub q{ [] },
9345b14c 77 lazy => 1,
78);
79
80sub last_exception { shift->exception_stack->[-1] }
81
f9080e45 82sub throw_exception { shift->storage->throw_exception (@_) }
83
9345b14c 84sub run {
85 my $self = shift;
86
9345b14c 87 $self->_reset_exception_stack;
7d534e68 88 $self->_set_failed_attempt_count(0);
89
90 my $cref = shift;
91
92 $self->throw_exception('run() requires a coderef to execute as its first argument')
93 if ( reftype($cref)||'' ) ne 'CODE';
94
9345b14c 95 my $storage = $self->storage;
96
7d534e68 97 return $cref->( @_ ) if (
98 $storage->{_in_do_block}
99 and
100 ! $self->wrap_txn
101 );
9345b14c 102
103 local $storage->{_in_do_block} = 1 unless $storage->{_in_do_block};
104
7d534e68 105 return $self->_run($cref, @_);
9345b14c 106}
107
108# this is the actual recursing worker
109sub _run {
7d534e68 110 # internal method - we know that both refs are strong-held by the
111 # calling scope of run(), hence safe to weaken everything
112 weaken( my $self = shift );
113 weaken( my $cref = shift );
9345b14c 114
7d534e68 115 my $args = @_ ? \@_ : [];
9345b14c 116
117 # from this point on (defined $txn_init_depth) is an indicator for wrap_txn
118 # save a bit on method calls
119 my $txn_init_depth = $self->wrap_txn ? $self->storage->transaction_depth : undef;
120 my $txn_begin_ok;
121
122 my $run_err = '';
123
9345b14c 124 return preserve_context {
ddcc02d1 125 dbic_internal_try {
9345b14c 126 if (defined $txn_init_depth) {
7d534e68 127 $self->storage->txn_begin;
9345b14c 128 $txn_begin_ok = 1;
129 }
7d534e68 130 $cref->( @$args );
9345b14c 131 } catch {
132 $run_err = $_;
133 (); # important, affects @_ below
134 };
135 } replace => sub {
136 my @res = @_;
137
7d534e68 138 my $storage = $self->storage;
9345b14c 139
729656c5 140 if (
141 defined $txn_init_depth
142 and
143 ! is_exception $run_err
144 and
145 defined( my $cur_depth = $storage->transaction_depth )
146 ) {
9345b14c 147 my $delta_txn = (1 + $txn_init_depth) - $cur_depth;
148
149 if ($delta_txn) {
150 # a rollback in a top-level txn_do is valid-ish (seen in the wild and our own tests)
151 carp (sprintf
152 'Unexpected reduction of transaction depth by %d after execution of '
153 . '%s, skipping txn_commit()',
154 $delta_txn,
7d534e68 155 $cref,
9345b14c 156 ) unless $delta_txn == 1 and $cur_depth == 0;
157 }
158 else {
ddcc02d1 159 dbic_internal_try {
160 $storage->txn_commit;
161 1;
162 }
163 catch {
164 $run_err = $_;
165 };
9345b14c 166 }
167 }
168
169 # something above threw an error (could be the begin, the code or the commit)
841efcb3 170 if ( is_exception $run_err ) {
9345b14c 171
84efb6d7 172 # Attempt a rollback if we did begin in the first place
173 # Will append rollback error if possible
174 $storage->__delicate_rollback( \$run_err )
175 if $txn_begin_ok;
9345b14c 176
7d534e68 177 push @{ $self->exception_stack }, $run_err;
178
179 # this will throw if max_attempts is reached
180 $self->_set_failed_attempt_count($self->failed_attempt_count + 1);
9345b14c 181
182 # init depth of > 0 ( > 1 with AC) implies nesting - no retry attempt queries
183 $storage->throw_exception($run_err) if (
184 (
185 defined $txn_init_depth
186 and
187 # FIXME - we assume that $storage->{_dbh_autocommit} is there if
188 # txn_init_depth is there, but this is a DBI-ism
189 $txn_init_depth > ( $storage->{_dbh_autocommit} ? 0 : 1 )
7d534e68 190 ) or ! $self->retry_handler->($self)
9345b14c 191 );
192
9345b14c 193 # we got that far - let's retry
7d534e68 194 carp( sprintf 'Retrying %s (attempt %d) after caught exception: %s',
195 $cref,
196 $self->failed_attempt_count + 1,
9345b14c 197 $run_err,
7d534e68 198 ) if $self->retry_debug;
9345b14c 199
200 $storage->ensure_connected;
201 # if txn_depth is > 1 this means something was done to the
4a0eed52 202 # original $dbh, otherwise we would not get past the preceding if()
9345b14c 203 $storage->throw_exception(sprintf
204 'Unexpected transaction depth of %d on freshly connected handle',
205 $storage->transaction_depth,
206 ) if (defined $txn_init_depth and $storage->transaction_depth);
207
7d534e68 208 return $self->_run($cref, @$args);
9345b14c 209 }
210
211 return wantarray ? @res : $res[0];
212 };
213}
214
a2bd3796 215=head1 FURTHER QUESTIONS?
9345b14c 216
a2bd3796 217Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
9345b14c 218
a2bd3796 219=head1 COPYRIGHT AND LICENSE
9345b14c 220
a2bd3796 221This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
222by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
223redistribute it and/or modify it under the same terms as the
224L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
9345b14c 225
226=cut
227
2281;