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.

README  view on Meta::CPAN


      * 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

README  view on Meta::CPAN

    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

t/basic.t  view on Meta::CPAN


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,

t/basic.t  view on Meta::CPAN

        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,

t/basic.t  view on Meta::CPAN

        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,

t/basic.t  view on Meta::CPAN

    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.485 second using v1.01-cache-2.11-cpan-0d8aa00de5b )