Algorithm-Backoff-RetryTimeouts
view release on metacpan or search on metacpan
CONTRIBUTING.md view on Meta::CPAN
this file is usually handled by [Carton](https://metacpan.org/pod/Carton).
Installing Carton can be done with any CPAN client into whichever version of perl you want to use.
If you don't have permission, or just don't want to install things into your system perl, you can use [App::Plenv](https://github.com/tokuhirom/plenv) and the [perl-build](https://github.com/tokuhirom/perl-build) plugin, or [Perlbrew](https://perlbre...
Once you have a version of perl to use for testing this, if you don't have a preference for a CPAN client, we recommend you use [cpanminus](https://metacpan.org/pod/App::cpanminus#Installing-to-system-perl).
With `plenv` you are able to get cpanminus with `plenv install-cpanm`.
Otherwise, quoting the cpanminus quickstart instructions:
> Quickstart: Run the following command and it will install itself for you.
> You might want to run it as a root with sudo if you want to
> install to places like /usr/local/bin.
>
> `% curl -L https://cpanmin.us | perl - App::cpanminus`
>
> If you don't have curl but wget, replace `curl -L` with `wget -O -`.
Once you have cpanminus, you can install Carton with:
cpanm Carton
With `carton` available, you can run `make test` and make sure tests pass. If they do, you're ready to start making changes and get ready to create a Pull Request.
### Using Dist::Zilla
The release of this distribution is managed by [Dist::Zilla](http://dzil.org) which provides a lot of benefits for managing releases and modules at the expense of longer a learning curve.
However, you probably don't need it unless you want to build a release or run author tests.
In order to work with Dist::Zilla's `dzil` you will need to run `carton install` manually as the Makefile uses `--without develop` to avoid unnecessary dependencies.
Once those dependencies are installed, you need to use `carton exec dzil` so it can find them.
* Adjustable timeouts - Providing an adjustable timeout after each
request solves the opposite problem of exponential backoffs: slower,
unresponsive errors that gobble up all of the max duration time in
one go. Each new timeout is a certain percentage of the time left.
Typical scenario
Here's an example scenario of the algorithm with existing defaults:
$retry_algo is created, and timer starts
Initial timeout is 25s
1st attempt fails instantly
$retry_algo says to wait 1.4s (±10% jitter), and use a timeout of 24.3s
2nd attempt fails instantly
$retry_algo says to wait 2s (±10% jitter), and use a timeout of 23.3s
Returns the last suggested delay, in seconds.
The delay will return -1 to suggest that the process should give up and
fail, if max_attempts or max_actual_duration have been reached.
timeout
my $timeout = $retry_algo->delay;
Returns the last suggested timeout, in seconds. If no attempts have
been logged, it will suggest an initial timeout to start with.
This will be a floating-point number, so you may need to convert it to
an integer if your timeout system doesn't support decimals.
A timeout of -1 will be returned if max_actual_duration was forcefully
turned off.
SEE ALSO
Algorithm::Backoff - Base distro for this module
lib/Algorithm/Backoff/RetryTimeouts.pm view on Meta::CPAN
#pod opposite problem of exponential backoffs: slower, unresponsive errors that gobble up all
#pod of the max duration time in one go. Each new timeout is a certain percentage of the time
#pod left.
#pod
#pod =back
#pod
#pod =head2 Typical scenario
#pod
#pod Here's an example scenario of the algorithm with existing defaults:
#pod
#pod $retry_algo is created, and timer starts
#pod
#pod Initial timeout is 25s
#pod
#pod 1st attempt fails instantly
#pod
#pod $retry_algo says to wait 1.4s (±10% jitter), and use a timeout of 24.3s
#pod
#pod 2nd attempt fails instantly
#pod
#pod $retry_algo says to wait 2s (±10% jitter), and use a timeout of 23.3s
lib/Algorithm/Backoff/RetryTimeouts.pm view on Meta::CPAN
sub delay {
my $self = shift;
return $self->{_prev_delay} // 0;
}
#pod =head2 timeout
#pod
#pod my $timeout = $retry_algo->delay;
#pod
#pod Returns the last suggested timeout, in seconds. If no attempts have been logged,
#pod it will suggest an initial timeout to start with.
#pod
#pod This will be a floating-point number, so you may need to convert it to an integer if your
#pod timeout system doesn't support decimals.
#pod
#pod A timeout of C<-1> will be returned if C<max_actual_duration> was forcefully turned off.
#pod
#pod =cut
sub timeout {
my $self = shift;
lib/Algorithm/Backoff/RetryTimeouts.pm view on Meta::CPAN
my $timeout = $max_time * $timeout_factor;
$timeout = $self->_add_timeout_jitter($timeout) if $self->{timeout_jitter_factor};
$timeout = $min_time if $min_time > $timeout;
return $timeout;
}
sub _set_last_timeout {
my ($self, $delay, $timestamp) = @_;
my $start_time = $self->{_start_timestamp};
my $min_time = $self->{min_adjust_timeout};
my $max_time = $self->{max_actual_duration};
my $timeout_factor = $self->{adjust_timeout_factor};
return ($delay // 0, -1) unless defined $start_time && $max_time;
$timestamp //= $self->{_last_timestamp} // $self->{_start_timestamp};
# Calculate initial timeout
my $actual_time_used = $timestamp - $start_time;
my $actual_time_left = $max_time - $actual_time_used;
my $timeout = $actual_time_left * $timeout_factor;
# Ensure the delay+timeout time isn't going to go over the limit
$delay //= 0;
my $max_delay = $actual_time_left * (1 - $timeout_factor);
$delay = $max_delay if $delay > $max_delay;
# Re-adjust the timeout based on the final delay and min timeout setting
$timeout = ($actual_time_left - $delay) * $timeout_factor;
lib/Algorithm/Backoff/RetryTimeouts.pm view on Meta::CPAN
# See https://github.com/perlancar/perl-Algorithm-Backoff/issues/1
$self->{_last_delay} = $self->{_prev_delay} //= 0;
return $self->SUPER::_consider_actual_delay(@_);
}
sub _success_or_failure {
my ($self, $is_success, $timestamp) = @_;
# If this is the first time, the _last_timestamp should be set to the start, not
# $timestamp. This will prevent issues with the first attempt causing unnecessary
# delays (ie: waiting 1.4s after the first attempt took longer than that).
$self->{_last_timestamp} //= $self->{_start_timestamp};
my $delay = $self->SUPER::_success_or_failure($is_success, $timestamp);
return $self->_set_last_timeout($delay, $timestamp);
}
#pod =head1 SEE ALSO
#pod
#pod L<Algorithm::Backoff> - Base distro for this module
#pod
#pod =cut
lib/Algorithm/Backoff/RetryTimeouts.pm view on Meta::CPAN
opposite problem of exponential backoffs: slower, unresponsive errors that gobble up all
of the max duration time in one go. Each new timeout is a certain percentage of the time
left.
=back
=head2 Typical scenario
Here's an example scenario of the algorithm with existing defaults:
$retry_algo is created, and timer starts
Initial timeout is 25s
1st attempt fails instantly
$retry_algo says to wait 1.4s (±10% jitter), and use a timeout of 24.3s
2nd attempt fails instantly
$retry_algo says to wait 2s (±10% jitter), and use a timeout of 23.3s
lib/Algorithm/Backoff/RetryTimeouts.pm view on Meta::CPAN
Returns the last suggested delay, in seconds.
The delay will return C<-1> to suggest that the process should give up and fail, if
C<max_attempts> or C<max_actual_duration> have been reached.
=head2 timeout
my $timeout = $retry_algo->delay;
Returns the last suggested timeout, in seconds. If no attempts have been logged,
it will suggest an initial timeout to start with.
This will be a floating-point number, so you may need to convert it to an integer if your
timeout system doesn't support decimals.
A timeout of C<-1> will be returned if C<max_actual_duration> was forcefully turned off.
=head1 SEE ALSO
L<Algorithm::Backoff> - Base distro for this module
use Algorithm::Backoff::RetryTimeouts;
my $rt;
my $time = 0;
my $sqrt2 = sqrt(2);
subtest "Base defaults" => sub {
$rt = Algorithm::Backoff::RetryTimeouts->new(
# for the unit tests
_start_timestamp => 0,
jitter_factor => 0,
timeout_jitter_factor => 0,
);
$time = 0;
is($rt->timeout, 25, 'Initial timeout: 25');
# 1: one second attempt
test_attempt(
attempt_time => 1,
expected_delay => -1,
expected_timeout => 5
);
};
subtest "attr: adjust_timeout_factor" => sub {
$rt = Algorithm::Backoff::RetryTimeouts->new(
adjust_timeout_factor => 0.25,
# for the unit tests
_start_timestamp => 0,
jitter_factor => 0,
timeout_jitter_factor => 0,
);
$time = 0;
is($rt->timeout, 12.5, 'Initial timeout: 12.5');
# 1: one second attempt
test_attempt(
attempt_time => 1,
expected_timeout => 5,
);
};
subtest "attr: min_adjust_timeout" => sub {
$rt = Algorithm::Backoff::RetryTimeouts->new(
adjust_timeout_factor => 0.75, # just to make this faster
min_adjust_timeout => 0,
# for the unit tests
_start_timestamp => 0,
jitter_factor => 0,
timeout_jitter_factor => 0,
);
$time = 0;
is($rt->timeout, 37.5, 'Initial timeout: 37.5');
# 1: full timeout
test_attempt(
expected_delay => 0,
test_attempt(
expected_delay => -1,
expected_timeout => 0.001,
);
};
subtest "Jitter factors" => sub {
$rt = Algorithm::Backoff::RetryTimeouts->new(
max_attempts => 0,
consider_actual_delay => 0,
_start_timestamp => 0,
jitter_factor => 0.1,
timeout_jitter_factor => 0.1,
);
# Calculate an average of 1000 hits
my @timeouts;
push @timeouts, $rt->timeout for 1 .. 1000;
my @delays;
( run in 0.305 second using v1.01-cache-2.11-cpan-0d8aa00de5b )