line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Algorithm::Backoff::RetryTimeouts; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
70843
|
use utf8; |
|
1
|
|
|
|
|
26
|
|
|
1
|
|
|
|
|
6
|
|
4
|
1
|
|
|
1
|
|
32
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
21
|
|
5
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
26
|
|
6
|
|
|
|
|
|
|
|
7
|
1
|
|
|
1
|
|
492
|
use Algorithm::Backoff::Exponential; |
|
1
|
|
|
|
|
4978
|
|
|
1
|
|
|
|
|
35
|
|
8
|
1
|
|
|
1
|
|
7
|
use base qw< Algorithm::Backoff::Exponential >; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
159
|
|
9
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
691
|
use Storable qw< dclone >; |
|
1
|
|
|
|
|
3159
|
|
|
1
|
|
|
|
|
67
|
|
11
|
1
|
|
|
1
|
|
8
|
use Time::HiRes qw< time >; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
12
|
|
|
|
|
|
|
|
13
|
1
|
|
|
1
|
|
655
|
use namespace::clean; |
|
1
|
|
|
|
|
16162
|
|
|
1
|
|
|
|
|
6
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
# ABSTRACT: A backoff-style retry algorithm with adjustable timeout support |
16
|
1
|
|
|
1
|
|
686
|
use version; |
|
1
|
|
|
|
|
1908
|
|
|
1
|
|
|
|
|
6
|
|
17
|
|
|
|
|
|
|
our $VERSION = 'v1.0.0'; # VERSION |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
#pod =head1 SYNOPSIS |
20
|
|
|
|
|
|
|
#pod |
21
|
|
|
|
|
|
|
#pod use Algorithm::Backoff::RetryTimeouts; |
22
|
|
|
|
|
|
|
#pod |
23
|
|
|
|
|
|
|
#pod my $retry_algo = Algorithm::Backoff::RetryTimeouts->new( |
24
|
|
|
|
|
|
|
#pod # common adjustments (defaults shown) |
25
|
|
|
|
|
|
|
#pod max_attempts => 8, |
26
|
|
|
|
|
|
|
#pod max_actual_duration => 50, |
27
|
|
|
|
|
|
|
#pod jitter_factor => 0.1, |
28
|
|
|
|
|
|
|
#pod timeout_jitter_factor => 0.1, |
29
|
|
|
|
|
|
|
#pod adjust_timeout_factor => 0.5, |
30
|
|
|
|
|
|
|
#pod min_adjust_timeout => 5, |
31
|
|
|
|
|
|
|
#pod |
32
|
|
|
|
|
|
|
#pod # other defaults |
33
|
|
|
|
|
|
|
#pod initial_delay => sqrt(2), |
34
|
|
|
|
|
|
|
#pod exponent_base => sqrt(2), |
35
|
|
|
|
|
|
|
#pod delay_on_success => 0, |
36
|
|
|
|
|
|
|
#pod min_delay => 0, |
37
|
|
|
|
|
|
|
#pod max_delay => undef, |
38
|
|
|
|
|
|
|
#pod consider_actual_delay => 1, |
39
|
|
|
|
|
|
|
#pod ); |
40
|
|
|
|
|
|
|
#pod |
41
|
|
|
|
|
|
|
#pod my ($delay, $timeout); |
42
|
|
|
|
|
|
|
#pod $timeout = $retry_algo->timeout; |
43
|
|
|
|
|
|
|
#pod |
44
|
|
|
|
|
|
|
#pod my $is_successful = 0; |
45
|
|
|
|
|
|
|
#pod while (!$is_successful) { |
46
|
|
|
|
|
|
|
#pod $actionee->timeout( $timeout ); |
47
|
|
|
|
|
|
|
#pod $is_successful = $actionee->do_the_thing; |
48
|
|
|
|
|
|
|
#pod |
49
|
|
|
|
|
|
|
#pod ($delay, $timeout) = $is_successful ? $retry_algo->success : $retry_algo->failure; |
50
|
|
|
|
|
|
|
#pod die "Ran out of time" if $delay == -1; |
51
|
|
|
|
|
|
|
#pod sleep $delay; |
52
|
|
|
|
|
|
|
#pod } |
53
|
|
|
|
|
|
|
#pod |
54
|
|
|
|
|
|
|
#pod =head1 DESCRIPTION |
55
|
|
|
|
|
|
|
#pod |
56
|
|
|
|
|
|
|
#pod This module is a subclass of L that adds support for |
57
|
|
|
|
|
|
|
#pod adjustable timeouts during each retry. This also comes with a sane set of defaults as a |
58
|
|
|
|
|
|
|
#pod good baseline for most kinds of retry operations. |
59
|
|
|
|
|
|
|
#pod |
60
|
|
|
|
|
|
|
#pod A combination of features solves for most problems that would arise from retry operations: |
61
|
|
|
|
|
|
|
#pod |
62
|
|
|
|
|
|
|
#pod =over |
63
|
|
|
|
|
|
|
#pod |
64
|
|
|
|
|
|
|
#pod =item * |
65
|
|
|
|
|
|
|
#pod |
66
|
|
|
|
|
|
|
#pod B - Forces the algorithm to give up if repeated attempts don't yield |
67
|
|
|
|
|
|
|
#pod success. |
68
|
|
|
|
|
|
|
#pod |
69
|
|
|
|
|
|
|
#pod =item * |
70
|
|
|
|
|
|
|
#pod |
71
|
|
|
|
|
|
|
#pod B - Forces the algorithm to give up if no successes happen within a |
72
|
|
|
|
|
|
|
#pod certain time frame. |
73
|
|
|
|
|
|
|
#pod |
74
|
|
|
|
|
|
|
#pod =item * |
75
|
|
|
|
|
|
|
#pod |
76
|
|
|
|
|
|
|
#pod B - A C exponential delay keeps single retries from waiting |
77
|
|
|
|
|
|
|
#pod too long, while spreading out repeated retries that may fail too quickly and run out of |
78
|
|
|
|
|
|
|
#pod max attempts. This also decreases the congestion that happens with repeated attempts. |
79
|
|
|
|
|
|
|
#pod |
80
|
|
|
|
|
|
|
#pod =item * |
81
|
|
|
|
|
|
|
#pod |
82
|
|
|
|
|
|
|
#pod B - Adding random jitter to the retry delays solves for the Thundering Herd |
83
|
|
|
|
|
|
|
#pod problem. |
84
|
|
|
|
|
|
|
#pod |
85
|
|
|
|
|
|
|
#pod =item * |
86
|
|
|
|
|
|
|
#pod |
87
|
|
|
|
|
|
|
#pod B - Providing an adjustable timeout after each request solves the |
88
|
|
|
|
|
|
|
#pod opposite problem of exponential backoffs: slower, unresponsive errors that gobble up all |
89
|
|
|
|
|
|
|
#pod of the max duration time in one go. Each new timeout is a certain percentage of the time |
90
|
|
|
|
|
|
|
#pod left. |
91
|
|
|
|
|
|
|
#pod |
92
|
|
|
|
|
|
|
#pod =back |
93
|
|
|
|
|
|
|
#pod |
94
|
|
|
|
|
|
|
#pod =head2 Typical scenario |
95
|
|
|
|
|
|
|
#pod |
96
|
|
|
|
|
|
|
#pod Here's an example scenario of the algorithm with existing defaults: |
97
|
|
|
|
|
|
|
#pod |
98
|
|
|
|
|
|
|
#pod $retry_algo is created, and timer starts |
99
|
|
|
|
|
|
|
#pod |
100
|
|
|
|
|
|
|
#pod Initial timeout is 25s |
101
|
|
|
|
|
|
|
#pod |
102
|
|
|
|
|
|
|
#pod 1st attempt fails instantly |
103
|
|
|
|
|
|
|
#pod |
104
|
|
|
|
|
|
|
#pod $retry_algo says to wait 1.4s (±10% jitter), and use a timeout of 24.3s |
105
|
|
|
|
|
|
|
#pod |
106
|
|
|
|
|
|
|
#pod 2nd attempt fails instantly |
107
|
|
|
|
|
|
|
#pod |
108
|
|
|
|
|
|
|
#pod $retry_algo says to wait 2s (±10% jitter), and use a timeout of 23.3s |
109
|
|
|
|
|
|
|
#pod |
110
|
|
|
|
|
|
|
#pod 3rd attempt fails after the full 23.3s timeout |
111
|
|
|
|
|
|
|
#pod |
112
|
|
|
|
|
|
|
#pod $retry_algo says to not wait (since the attempt already used up the delay), and use |
113
|
|
|
|
|
|
|
#pod a timeout of 11.7s |
114
|
|
|
|
|
|
|
#pod |
115
|
|
|
|
|
|
|
#pod 4th attempt succeeds |
116
|
|
|
|
|
|
|
#pod |
117
|
|
|
|
|
|
|
#pod =cut |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
our %SPEC = %{ dclone \%Algorithm::Backoff::Exponential::SPEC }; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
{ |
122
|
|
|
|
|
|
|
my $args = $SPEC{new}{args}; |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# Our defaults |
125
|
|
|
|
|
|
|
$args->{consider_actual_delay}{default} = 1; |
126
|
|
|
|
|
|
|
$args->{max_attempts }{default} = 8; |
127
|
|
|
|
|
|
|
$args->{max_actual_duration }{default} = 50; |
128
|
|
|
|
|
|
|
$args->{jitter_factor }{default} = 0.1; |
129
|
|
|
|
|
|
|
$args->{initial_delay }{default} = sqrt(2); |
130
|
|
|
|
|
|
|
$args->{exponent_base }{default} = sqrt(2); |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# No need to require what already has a default |
133
|
|
|
|
|
|
|
$args->{initial_delay}{req} = 0; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# New arguments |
136
|
|
|
|
|
|
|
$args->{adjust_timeout_factor} = { |
137
|
|
|
|
|
|
|
summary => 'How much of the time left to use in the adjustable timeout', |
138
|
|
|
|
|
|
|
schema => ['ufloat*', between=>[0, 1]], |
139
|
|
|
|
|
|
|
default => 0.5, |
140
|
|
|
|
|
|
|
}; |
141
|
|
|
|
|
|
|
$args->{min_adjust_timeout} = { |
142
|
|
|
|
|
|
|
summary => 'Minimum adjustable timeout, in seconds', |
143
|
|
|
|
|
|
|
schema => 'ufloat*', |
144
|
|
|
|
|
|
|
default => 5, |
145
|
|
|
|
|
|
|
}; |
146
|
|
|
|
|
|
|
$args->{timeout_jitter_factor} = { |
147
|
|
|
|
|
|
|
summary => 'How much randomness to add to the adjustable timeout', |
148
|
|
|
|
|
|
|
schema => ['float*', between=>[0, 0.5]], |
149
|
|
|
|
|
|
|
default => 0.1, |
150
|
|
|
|
|
|
|
}; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
#pod =head1 CONSTRUCTOR |
154
|
|
|
|
|
|
|
#pod |
155
|
|
|
|
|
|
|
#pod The L<"new"|Algorithm::Backoff::Exponential/new> constructor takes all of the base options |
156
|
|
|
|
|
|
|
#pod from L. Some of the defaults are changed (also shown in |
157
|
|
|
|
|
|
|
#pod the L above), but otherwise function the same way. |
158
|
|
|
|
|
|
|
#pod |
159
|
|
|
|
|
|
|
#pod =over |
160
|
|
|
|
|
|
|
#pod |
161
|
|
|
|
|
|
|
#pod =item * L => I (default: 8) |
162
|
|
|
|
|
|
|
#pod |
163
|
|
|
|
|
|
|
#pod =item * L => I (default: 50) |
164
|
|
|
|
|
|
|
#pod |
165
|
|
|
|
|
|
|
#pod =item * L => I (default: 0.1) |
166
|
|
|
|
|
|
|
#pod |
167
|
|
|
|
|
|
|
#pod =item * L => I (default: C) |
168
|
|
|
|
|
|
|
#pod |
169
|
|
|
|
|
|
|
#pod =item * L => I (default: C) |
170
|
|
|
|
|
|
|
#pod |
171
|
|
|
|
|
|
|
#pod =item * L => I (default: 0) |
172
|
|
|
|
|
|
|
#pod |
173
|
|
|
|
|
|
|
#pod =item * L => I (default: 0) |
174
|
|
|
|
|
|
|
#pod |
175
|
|
|
|
|
|
|
#pod =item * L => I |
176
|
|
|
|
|
|
|
#pod |
177
|
|
|
|
|
|
|
#pod =item * L => I (default: 1) |
178
|
|
|
|
|
|
|
#pod |
179
|
|
|
|
|
|
|
#pod =back |
180
|
|
|
|
|
|
|
#pod |
181
|
|
|
|
|
|
|
#pod The following new options are added in this module: |
182
|
|
|
|
|
|
|
#pod |
183
|
|
|
|
|
|
|
#pod =over |
184
|
|
|
|
|
|
|
#pod |
185
|
|
|
|
|
|
|
#pod =item * adjust_timeout_factor => I (default: 0.5) |
186
|
|
|
|
|
|
|
#pod |
187
|
|
|
|
|
|
|
#pod How much of the remaining time to use for the next attempt's timeout, as a |
188
|
|
|
|
|
|
|
#pod factor between 0 and 1. |
189
|
|
|
|
|
|
|
#pod |
190
|
|
|
|
|
|
|
#pod In order to prevent a single attempt from using up all of the remaining time, an |
191
|
|
|
|
|
|
|
#pod adjustable timeout will force the attempt to only use a portion of the time. By default, |
192
|
|
|
|
|
|
|
#pod only 50% of the remaining time will be set as the next timeout value. |
193
|
|
|
|
|
|
|
#pod |
194
|
|
|
|
|
|
|
#pod =item * min_adjust_timeout => I (default: 5) |
195
|
|
|
|
|
|
|
#pod |
196
|
|
|
|
|
|
|
#pod Minimum timeout value, in seconds. |
197
|
|
|
|
|
|
|
#pod |
198
|
|
|
|
|
|
|
#pod This value bypasses any C checks, so the total time spent on |
199
|
|
|
|
|
|
|
#pod sleeping and attempts may end up exceeding that value by a small amount (up to |
200
|
|
|
|
|
|
|
#pod C). In this case, future failures will return |
201
|
|
|
|
|
|
|
#pod a delay of C<-1> as expected. |
202
|
|
|
|
|
|
|
#pod |
203
|
|
|
|
|
|
|
#pod =item * timeout_jitter_factor => I (default: 0.1) |
204
|
|
|
|
|
|
|
#pod |
205
|
|
|
|
|
|
|
#pod How much randomness to add to the adjustable timeout. |
206
|
|
|
|
|
|
|
#pod |
207
|
|
|
|
|
|
|
#pod Delay jitter may not be enough to desynchronize two processes that are consistently |
208
|
|
|
|
|
|
|
#pod timing out on the same problem. In those cases, the delay will usually be zero and won't |
209
|
|
|
|
|
|
|
#pod have any sort of jitter to solve the problem itself. A jitter factor against the timeout |
210
|
|
|
|
|
|
|
#pod will ensure simultaneous attempts have slightly different timeout windows. |
211
|
|
|
|
|
|
|
#pod |
212
|
|
|
|
|
|
|
#pod =back |
213
|
|
|
|
|
|
|
#pod |
214
|
|
|
|
|
|
|
#pod =head1 METHODS |
215
|
|
|
|
|
|
|
#pod |
216
|
|
|
|
|
|
|
#pod =head2 success |
217
|
|
|
|
|
|
|
#pod |
218
|
|
|
|
|
|
|
#pod my ($delay, $timeout) = $retry_algo->success([ $timestamp ]); |
219
|
|
|
|
|
|
|
#pod |
220
|
|
|
|
|
|
|
#pod Log a successful attempt. If not specified, C<$timestamp> defaults to current time. |
221
|
|
|
|
|
|
|
#pod Unlike the L, this method will return a list containing |
222
|
|
|
|
|
|
|
#pod both the L and the L for the next |
223
|
|
|
|
|
|
|
#pod attempt. |
224
|
|
|
|
|
|
|
#pod |
225
|
|
|
|
|
|
|
#pod =head2 failure |
226
|
|
|
|
|
|
|
#pod |
227
|
|
|
|
|
|
|
#pod my ($delay, $timeout) = $retry_algo->failure([ $timestamp ]); |
228
|
|
|
|
|
|
|
#pod |
229
|
|
|
|
|
|
|
#pod Log a failed attempt. If not specified, C<$timestamp> defaults to current time. |
230
|
|
|
|
|
|
|
#pod Unlike the L, this method will return a list containing |
231
|
|
|
|
|
|
|
#pod both the L and the L for the next |
232
|
|
|
|
|
|
|
#pod attempt. |
233
|
|
|
|
|
|
|
#pod |
234
|
|
|
|
|
|
|
#pod =cut |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub failure { |
237
|
1023
|
|
|
1023
|
1
|
4020
|
my ($self, $timestamp) = @_; |
238
|
1023
|
|
33
|
|
|
2198
|
$timestamp //= time; |
239
|
|
|
|
|
|
|
|
240
|
1023
|
|
|
|
|
2048
|
my ($delay, $timeout) = $self->SUPER::failure($timestamp); |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Fix certain values if the check failed max duration/attempts checks |
243
|
1023
|
|
66
|
|
|
2003
|
$timeout //= $self->timeout; |
244
|
1023
|
100
|
|
|
|
1870
|
if ($delay == -1) { |
245
|
3
|
|
|
|
|
7
|
$self->{_attempts}++; |
246
|
3
|
|
|
|
|
6
|
$self->{_last_timestamp} = $timestamp; |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
1023
|
|
|
|
|
1838
|
return ($delay, $timeout); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
#pod =head2 delay |
253
|
|
|
|
|
|
|
#pod |
254
|
|
|
|
|
|
|
#pod my $delay = $retry_algo->delay; |
255
|
|
|
|
|
|
|
#pod |
256
|
|
|
|
|
|
|
#pod Returns the last suggested delay, in seconds. |
257
|
|
|
|
|
|
|
#pod |
258
|
|
|
|
|
|
|
#pod The delay will return C<-1> to suggest that the process should give up and fail, if |
259
|
|
|
|
|
|
|
#pod C or C have been reached. |
260
|
|
|
|
|
|
|
#pod |
261
|
|
|
|
|
|
|
#pod =cut |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub delay { |
264
|
1059
|
|
|
1059
|
1
|
35936
|
my $self = shift; |
265
|
1059
|
|
100
|
|
|
2975
|
return $self->{_prev_delay} // 0; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
#pod =head2 timeout |
269
|
|
|
|
|
|
|
#pod |
270
|
|
|
|
|
|
|
#pod my $timeout = $retry_algo->delay; |
271
|
|
|
|
|
|
|
#pod |
272
|
|
|
|
|
|
|
#pod Returns the last suggested timeout, in seconds. If no attempts have been logged, |
273
|
|
|
|
|
|
|
#pod it will suggest an initial timeout to start with. |
274
|
|
|
|
|
|
|
#pod |
275
|
|
|
|
|
|
|
#pod This will be a floating-point number, so you may need to convert it to an integer if your |
276
|
|
|
|
|
|
|
#pod timeout system doesn't support decimals. |
277
|
|
|
|
|
|
|
#pod |
278
|
|
|
|
|
|
|
#pod A timeout of C<-1> will be returned if C was forcefully turned off. |
279
|
|
|
|
|
|
|
#pod |
280
|
|
|
|
|
|
|
#pod =cut |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub timeout { |
283
|
1047
|
|
|
1047
|
1
|
12268
|
my $self = shift; |
284
|
|
|
|
|
|
|
|
285
|
1047
|
|
|
|
|
1684
|
my $last_timeout = $self->{_last_timeout}; |
286
|
1047
|
|
|
|
|
1555
|
my $min_time = $self->{min_adjust_timeout}; |
287
|
1047
|
|
|
|
|
1577
|
my $max_time = $self->{max_actual_duration}; |
288
|
1047
|
|
|
|
|
1537
|
my $timeout_factor = $self->{adjust_timeout_factor}; |
289
|
|
|
|
|
|
|
|
290
|
1047
|
100
|
|
|
|
1964
|
return $last_timeout if defined $last_timeout; |
291
|
1004
|
50
|
|
|
|
1802
|
return -1 unless $max_time; |
292
|
|
|
|
|
|
|
|
293
|
1004
|
|
|
|
|
1519
|
my $timeout = $max_time * $timeout_factor; |
294
|
1004
|
100
|
|
|
|
2130
|
$timeout = $self->_add_timeout_jitter($timeout) if $self->{timeout_jitter_factor}; |
295
|
1004
|
50
|
|
|
|
2114
|
$timeout = $min_time if $min_time > $timeout; |
296
|
1004
|
|
|
|
|
2272
|
return $timeout; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
sub _set_last_timeout { |
300
|
1020
|
|
|
1020
|
|
1772
|
my ($self, $delay, $timestamp) = @_; |
301
|
|
|
|
|
|
|
|
302
|
1020
|
|
|
|
|
1498
|
my $start_time = $self->{_start_timestamp}; |
303
|
1020
|
|
|
|
|
1599
|
my $min_time = $self->{min_adjust_timeout}; |
304
|
1020
|
|
|
|
|
1451
|
my $max_time = $self->{max_actual_duration}; |
305
|
1020
|
|
|
|
|
1604
|
my $timeout_factor = $self->{adjust_timeout_factor}; |
306
|
1020
|
50
|
0
|
|
|
2989
|
return ($delay // 0, -1) unless defined $start_time && $max_time; |
|
|
|
33
|
|
|
|
|
307
|
|
|
|
|
|
|
|
308
|
1020
|
|
0
|
|
|
1795
|
$timestamp //= $self->{_last_timestamp} // $self->{_start_timestamp}; |
|
|
|
33
|
|
|
|
|
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# Calculate initial timeout |
311
|
1020
|
|
|
|
|
1621
|
my $actual_time_used = $timestamp - $start_time; |
312
|
1020
|
|
|
|
|
1434
|
my $actual_time_left = $max_time - $actual_time_used; |
313
|
1020
|
|
|
|
|
1639
|
my $timeout = $actual_time_left * $timeout_factor; |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
# Ensure the delay+timeout time isn't going to go over the limit |
316
|
1020
|
|
50
|
|
|
1703
|
$delay //= 0; |
317
|
1020
|
|
|
|
|
1561
|
my $max_delay = $actual_time_left * (1 - $timeout_factor); |
318
|
1020
|
100
|
|
|
|
1952
|
$delay = $max_delay if $delay > $max_delay; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# Re-adjust the timeout based on the final delay and min timeout setting |
321
|
1020
|
|
|
|
|
1496
|
$timeout = ($actual_time_left - $delay) * $timeout_factor; |
322
|
1020
|
100
|
|
|
|
2234
|
$timeout = $self->_add_timeout_jitter($timeout) if $self->{timeout_jitter_factor}; |
323
|
1020
|
100
|
|
|
|
2093
|
$timeout = $min_time if $min_time > $timeout; |
324
|
|
|
|
|
|
|
|
325
|
1020
|
|
|
|
|
1584
|
$self->{_prev_delay} = $delay; |
326
|
1020
|
|
|
|
|
1423
|
$self->{_last_timeout} = $timeout; |
327
|
|
|
|
|
|
|
|
328
|
1020
|
|
|
|
|
2590
|
return ($delay, $timeout); |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub _add_timeout_jitter { |
332
|
2000
|
|
|
2000
|
|
3346
|
my ($self, $timeout) = @_; |
333
|
2000
|
|
|
|
|
2932
|
my $jitter = $self->{timeout_jitter_factor}; |
334
|
2000
|
50
|
33
|
|
|
5528
|
return $timeout unless $timeout && $jitter; |
335
|
|
|
|
|
|
|
|
336
|
2000
|
|
|
|
|
3240
|
my $min = $timeout * (1 - $jitter); |
337
|
2000
|
|
|
|
|
3318
|
my $max = $timeout * (1 + $jitter); |
338
|
2000
|
|
|
|
|
4049
|
return $min + ($max - $min) * rand(); |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub _consider_actual_delay { |
342
|
20
|
|
|
20
|
|
340
|
my $self = shift; |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# See https://github.com/perlancar/perl-Algorithm-Backoff/issues/1 |
345
|
20
|
|
100
|
|
|
63
|
$self->{_last_delay} = $self->{_prev_delay} //= 0; |
346
|
|
|
|
|
|
|
|
347
|
20
|
|
|
|
|
55
|
return $self->SUPER::_consider_actual_delay(@_); |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
sub _success_or_failure { |
351
|
1020
|
|
|
1020
|
|
9826
|
my ($self, $is_success, $timestamp) = @_; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# If this is the first time, the _last_timestamp should be set to the start, not |
354
|
|
|
|
|
|
|
# $timestamp. This will prevent issues with the first attempt causing unnecessary |
355
|
|
|
|
|
|
|
# delays (ie: waiting 1.4s after the first attempt took longer than that). |
356
|
1020
|
|
66
|
|
|
1950
|
$self->{_last_timestamp} //= $self->{_start_timestamp}; |
357
|
|
|
|
|
|
|
|
358
|
1020
|
|
|
|
|
1979
|
my $delay = $self->SUPER::_success_or_failure($is_success, $timestamp); |
359
|
1020
|
|
|
|
|
27448
|
return $self->_set_last_timeout($delay, $timestamp); |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
#pod =head1 SEE ALSO |
363
|
|
|
|
|
|
|
#pod |
364
|
|
|
|
|
|
|
#pod L - Base distro for this module |
365
|
|
|
|
|
|
|
#pod |
366
|
|
|
|
|
|
|
#pod =cut |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
1; |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
__END__ |