File Coverage

blib/lib/LWP/UserAgent/ExponentialBackoff.pm
Criterion Covered Total %
statement 76 84 90.4
branch 18 30 60.0
condition 3 3 100.0
subroutine 11 17 64.7
pod 4 16 25.0
total 112 150 74.6


line stmt bran cond sub pod time code
1 2     2   36952 use LWP::UserAgent;
  2         117946  
  2         2259  
2              
3             package LWP::UserAgent::ExponentialBackoff;
4             $VERSION = '0.04';
5             @ISA = ("LWP::UserAgent");
6             my @FAILCODES = qw(408 500 502 503 504);
7              
8             sub new {
9 1     1 1 1036 my ( $class, %cnf ) = @_;
10 1         4 my $before_request = delete $cnf{before_request};
11 1         4 my $after_request = delete $cnf{after_request};
12 1         2 my $sum = delete $cnf{sum};
13 1         3 my $retryCount = delete $cnf{retryCount};
14 1 50       5 $retryCount = 3 unless defined $retryCount;
15 1         3 my $minBackoff = delete $cnf{minBackoff};
16 1 50       4 $minBackoff = 3 unless defined $minBackoff;
17 1         3 my $maxBackoff = delete $cnf{maxBackoff};
18 1 50       6 $maxBackoff = 90 unless defined $maxBackoff;
19 1         3 my $deltaBackoff = delete $cnf{deltaBackoff};
20 1 50       4 $deltaBackoff = 3 unless defined $deltaBackoff;
21 1         2 my $tolerance = delete $cnf{tolerance};
22 1 50       3 $tolerance = .20 unless defined $tolerance;
23 1         2 my $failCodes = delete $cnf{failCodes};
24 1 50       6 $failCodes = { map { $_ => $_ } @FAILCODES } unless defined $failCodes;
  5         16  
25 1         12 my $self = $class->SUPER::new(@_);
26 1         28 $self = bless {
27 1         5556 %{$self},
28             sum => $sum,
29             retryCount => $retryCount,
30             minBackoff => $minBackoff,
31             maxBackoff => $maxBackoff,
32             tolerance => $tolerance,
33             deltaBackoff => $deltaBackoff,
34             deltaLow => $deltaLow,
35             deltaHigh => $deltaHigh,
36             failCodes => $failCodes
37             }, $class;
38 1         24 $self->deltas($tolerance);
39 1 50       6 $self->sum($sum) unless !defined $sum;
40 1 50       8 $self->before_request($before_request) unless !defined $before_request;
41 1 50       15 $self->after_request($after_request) unless !defined $after_request;
42 1         13 return $self;
43             }
44              
45             sub simple_request {
46 5     5 1 21727 my ( $self, @args ) = @_;
47 5         15 my $failCodes = $self->{failCodes};
48 5         9 my $currentRetryCount = 0;
49 5         21 my $before_c = $self->before_request;
50 5         52 my $after_c = $self->after_request;
51 5         77 my $retryInterval = 0;
52            
53 7         44 do {
54 8 50       56 $before_c and $before_c->( $self, \@args, $retryInterval);
55 8         21003634 sleep $retryInterval;
56 8         101 $response = $self->SUPER::simple_request(@args);
57 8 50       1864051 $after_c and $after_c->( $self, \@args, $retryInterval, $response);
58 8         2459 $code = $response->code();
59 8         118 $currentRetryCount++;
60             }while ( ( $retryInterval = $self->retry($currentRetryCount-1) )
61 5   100     11 && ${$failCodes}{$code} );
62            
63 5         27 return $response;
64             }
65              
66             sub retry {
67 8     8 0 19 my ( $self, $currentRetryCount ) = @_;
68 8         22 my $retryCount = $self->{retryCount};
69 8         22 my $minBackoff = $self->{minBackoff};
70 8         22 my $maxBackoff = $self->{maxBackoff};
71 8         19 my $deltaLow = $self->{deltaLow};
72 8         15 my $deltaHigh = $self->{deltaHigh};
73 8         18 my $deltaBackoff = $self->{deltaBackoff};
74              
75 8 100       36 if ( $currentRetryCount < $retryCount ) {
76              
77             #Calculate Exponential backoff with tolerance (deltaLow & deltaHigh)
78 7         16 my $r = $deltaBackoff;
79 7 100       30 if ( $deltaHigh - $deltaLow != 0 ) {
80 3         108 $r = rand( $deltaHigh - $deltaLow ) + $deltaLow;
81             }
82 7         31 $increment = ( 2**$currentRetryCount - 1 ) * $r + $minBackoff;
83 7 50       28 $retryInterval = $increment <= $maxBackoff ? $increment : $maxBackoff;
84             }
85             else {
86 1         3 $retryInterval = 0;
87             }
88 8         45 return $retryInterval;
89             }
90              
91 0     0 0 0 sub retryCount { shift->_elem( 'retryCount', @_ ); }
92 0     0 0 0 sub minBackoff { shift->_elem( 'minBackoff', @_ ); }
93 0     0 0 0 sub maxBackoff { shift->_elem( 'maxBackoff', @_ ); }
94 6     6 0 650 sub failCodes { shift->_elem( 'failCodes', @_ ) }
95 6     6 1 42 sub before_request { shift->_elem( 'before_request', @_ ) }
96 6     6 1 23 sub after_request { shift->_elem( 'after_request', @_ ) }
97              
98             sub tolerance {
99 1     1 0 6 my ( $self, $tolerance ) = @_;
100 1         3 $self->{tolerance} = $tolerance;
101 1         8 $self->deltas($tolerance);
102             }
103              
104             sub deltas {
105 2     2 0 6 my ( $self, $tolerance ) = @_;
106 2 100       10 if ( $tolerance == 0 ) {
107 1         3 $self->{deltaLow} = 0;
108 1         6 $self->{deltaHigh} = 0;
109             }
110             else {
111 1         6 $self->{deltaLow} = $self->{deltaBackoff} * ( 1 - $tolerance );
112 1         4 $self->{deltaHigh} = $self->{deltaBackoff} * ( 1 + $tolerance );
113             }
114             }
115              
116 0     0 0 0 sub deltaBackoff { shift->_elem( 'deltaBackoff', @_ ); }
117              
118             sub addFailCodes {
119 0     0 0 0 my ( $self, $code ) = @_;
120 0         0 $self->{failCodes}->{$code} = $code;
121             }
122              
123             sub delFailCodes {
124 0     0 0 0 my ( $self, $code ) = @_;
125 0         0 delete $self->{failCodes}->{$code};
126             }
127              
128             # Given sum and deltaBackoff, compute retryCount and maxBackoff such that total retryIntervals will always be equal to or "slightly" greater than sum.
129             sub sum {
130 3     3 0 2036 my ( $self, $sum ) = @_;
131 3         17 $self->{retryCount} = log2( $sum / $self->{deltaBackoff} + 1 );
132              
133             # maxBackoff should be at least as big as the largest retry interval which is never bigger than the sum, so just make it equal the sum
134 3         6 $self->{maxBackoff} = $sum;
135 3         11 $self->{sum} = $sum;
136             }
137              
138             sub log2 {
139 3     3 0 8 my $n = shift;
140 3         25 return log($n) / log(2);
141             }
142             1;
143             __END__