File Coverage

blib/lib/DBIx/Class/Storage/BlockRunner.pm
Criterion Covered Total %
statement 70 72 97.2
branch 27 34 79.4
condition 18 23 78.2
subroutine 13 14 92.8
pod 0 3 0.0
total 128 146 87.6


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