The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Action::Retry - Module to try to perform an action, with various ways of retrying and sleeping between retries.

VERSION

version 0.24

SYNOPSIS

 # Simple usage, will attempt to run the code, retrying if it dies, retrying
 # 10 times max, sleeping 1 second between retries

 # functional interface
 use Action::Retry qw(retry);
 retry { do_stuff };

 # OO interface
 use Action::Retry;
 Action::Retry->new( attempt_code => sub { do_stuff; } )->run();



 # Same, but sleep time is doubling each time, and arguments passed to the
 # attempted code

 # OO interface
 my $action = Action::Retry->new(
   attempt_code => sub { my ($num, $str) = @_; ... },
   strategy => 'Linear',
 );
 my $result = $action->run(42, "foo");

 # functional interface
 retry { my ($num, $str) = @_;... } strategy => 'Linear';



 # Same, but sleep time is following the Fibonacci sequence

 # OO interface
 my $action = Action::Retry->new(
   attempt_code => sub { ... },
   strategy => 'Fibonacci',
 );
 $action->run();

 # functional interface
 retry { ... } strategy => 'Fibonacci';



 # The code to check if the attempt succeeded can be customized. Strategies
 # can take arguments. Code on failure can be specified.

 # OO way
 my $action = Action::Retry->new(
   attempt_code => sub { ... },
   retry_if_code => sub { $_[0] =~ /Connection lost/ || $_[1]->{attempt_result} > 20 },
   strategy => { Fibonacci => { multiplicator => 2000,
                                initial_term_index => 3,
                                max_retries_number => 5,
                              }
               },
   on_failure_code => sub { say "Given up retrying" },
 );
 $action->run();

 # functional way
 retry { ...}
   retry_if_code => sub { ... },
   strategy => { Fibonacci => { multiplicator => 2000,
                                initial_term_index => 3,
                                max_retries_number => 5,
                              }
               },
   on_failure_code => sub { ... };



 # Retry code in non-blocking way

 # OO way
 my $action = Action::Retry->new(
   attempt_code => sub { ...},
   non_blocking => 1,
 );
 while (1) {
   # if the action failed, it doesn't sleep
   # next time it's called, it won't do anything until it's time to retry
   $action->run();
   # do something else while time goes on
 }

ATTRIBUTES

attempt_code

ro, CodeRef, required

The code to run to attempt doing the action. Will be evaluated taking care of the caller's context. It will receive parameters that were passed to run()

retry_if_code

ro, CodeRef

The code to run to check if we need to retry the action. It defaults to:

# Returns true if there were an exception evaluating to something true
sub { $_[0] }

It will be given these arguments:

  • as first argument, a scalar which is the value of any exception that were raised by the attempt_code. Otherwise, undef.

  • as second argument, a HashRef, which contains these keys:

    action_retry

    it's a reference on the ActionRetry instance. That way you can have access to the strategy and other attributes.

    attempt_result

    It's a scalar, which is the result of attempt_code. If attempt_code returned a list, then the scalar is the reference on this list.

    attempt_parameters

    It's the reference on the parameters that were given to attempt_code.

retry_if_code return value will be interpreted as a boolean : true return value means the execution of attempt_code was a failure and it needs to be retried. False means it went well.

Here is an example of code that gets the arguments properly:

my $action = Action::Retry->new(
  attempt_code => sub { do_stuff; } )->run();
  attempt_code => sub { map { $_ * 2 } @_ }
  retry_if_code => sub {
    my ($error, $h) = @_;

    my $attempt_code_result = $h->{attempt_result};
    my $attempt_code_params = $h->{attempt_parameters};

    my @results = @$attempt_code_result;
    # will contains (2, 4);

    my @original_parameters = @$attempt_code_params;
    # will contains (1, 2);

  }
);
my @results = $action->run(1, 2);

on_failure_code

ro, CodeRef, optional

If given, will be executed when retries are given up.

It will be given the same arguments as retry_if_code. See retry_if_code for their descriptions

strategy

ro, defaults to 'Constant'

The strategy for managing retrying times, sleeping, and giving up. It must be an object that does the Action::Retry::Strategy role.

This attribute has a coercion from strings and hashrefs. If you pass a string, it will be treated as a class name (under Action::Retry::Strategy::, unless it is prefxed with a +) to instantiate.

If you pass a hashref, the first key will be treated as a class name as above, and the value of that key will be treated as the args to pass to new.

Some existing stragies classes are Action::Retry::Strategy::Constant, Action::Retry::Strategy::Fibonacci, Action::Retry::Strategy::Linear.

Defaults to 'Constant'

non_blocking

ro, defaults to 0

If true, the instance will be in a pseudo non blocking mode. In this mode, the run() function behaves a bit differently: instead of sleeping between retries, the run() command will immediately return. Subsequent call to run() will immediately return, until the time to sleep has been elapsed. This allows to do things like that:

my $action = Action::Retry->new( ... , non_blocking => 1 );
while (1) {
  # if the action failed, it doesn't sleep
  # next time it's called, it won't do anything until it's time to retry
  $action->run();
  # do something else while time goes on
}

If you need a more advanced non blocking mode and callbacks, then look at AnyEvent::Retry

METHODS

run

Does the following:

step 1

Runs the attempt_code CodeRef in the proper context in an eval {} block, saving $@ in $error.

step 2

Runs the retry_if_code CodeRef in scalar context, giving it as arguments $error, and the return values of attempt_code. If it returns true, we consider that it was a failure, and move to step 3. Otherwise, we consider it means success, and return the return values of attempt_code.

step 3

Ask the strategy if it's still useful to retry. If yes, sleep accordingly, and go back to step 2. If not, go to step 4.

step 4

Runs the on_failure_code CodeRef in the proper context, giving it as arguments $error, and the return values of attempt_code, and returns the results back to the caller.

Arguments passed to run() will be passed to attempt_code. They will also passed to on_failure_code as well if the case arises.

retry

retry { ..code.. } some => 'arguments';

Is equivalent to

Action::Retry->new(attempt_code => sub { ..code.. }, some => arguments )->run();

A functional interface, alternative to the OO interface.

SRATEGIES

Here are the strategies currently included by default. Check their documentation for more details.

Action::Retry::Strategy::Constant

Provides a simple constant sleep time strategy

Action::Retry::Strategy::Fibonacci

Provides an incremental constant sleep time strategy following Fibonacci sequence

Action::Retry::Strategy::Linear

Provides a linear incrementing sleep time strategy

SEE ALSO

I created this module because the other related modules I found didn't exactly do what I wanted. Here is the list and why:

Retry

No custom checking code. No retry strategies. Can't sleep under one second. No non-blocking mode. No custom failure code.

Sub::Retry

No retry strategies. Can't sleep under one second. Retry code is passed the results of the attempt code, but not the exception. No non-blocking mode. No custom failure code.

Attempt

No custom checking code. Strange exception catching behavior. No retry strategies. No non-blocking mode. No custom failure code.

AnyEvent::Retry

Depends on AnyEvent, and Moose. Strategies are less flexibles, and they don't have sleep timeouts (only max tries).

AUTHOR

Damien "dams" Krotkine

COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Damien "dams" Krotkine.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.