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