Action-Retry
view release on metacpan or search on metacpan
lib/Action/Retry.pm view on Meta::CPAN
use base 'Exporter';
our @EXPORT_OK = qw(retry);
# export by default if run from command line
our @EXPORT = ((caller())[1] eq '-e' ? @EXPORT_OK : ());
use Moo;
has attempt_code => (
is => 'ro',
required => 1,
isa => sub { ref $_[0] eq 'CODE' },
);
has retry_if_code => (
is => 'ro',
required => 1,
isa => sub { ref $_[0] eq 'CODE' },
default => sub { sub { $_[0] }; },
);
has on_failure_code => (
is => 'ro',
isa => sub { ref $_[0] eq 'CODE' },
predicate => 1,
);
has strategy => (
is => 'ro',
default => sub { 'Constant' },
coerce => sub {
my $attr = $_[0];
blessed($attr)
and return $attr;
my $class_name = $attr;
my $constructor_params = {};
if (ref $attr eq 'HASH') {
$class_name = (keys %$attr)[0];
$constructor_params = $attr->{$class_name};
}
$class_name = $class_name =~ /^\+(.+)$/ ? $1 : "Action::Retry::Strategy::$class_name";
return use_module($class_name)->new($constructor_params);
},
isa => sub { $_[0]->does('Action::Retry::Strategy') or croak 'Should consume the Action::Retry::Strategy role' },
);
has non_blocking => (
is => 'ro',
default => sub { 0 },
);
# For non blocking mode, store the timestamp after which we can retry
has _needs_sleeping_until => (
is => 'rw',
default => sub { 0 },
init_arg => undef,
);
sub run {
my $self = shift;
while(1) {
if (my $timestamp = $self->_needs_sleeping_until) {
# we can't retry until we have waited enough time
my ($seconds, $microseconds) = gettimeofday;
$seconds * 1000 + int($microseconds / 1000) >= $timestamp
or return;
$self->_needs_sleeping_until(0);
$self->strategy->next_step;
}
my $error;
my @attempt_result;
my $attempt_result;
my $wantarray;
if (wantarray) {
$wantarray = 1;
@attempt_result = eval { $self->attempt_code->(@_) };
$error = $@;
} elsif ( ! defined wantarray ) {
eval { $self->attempt_code->(@_) };
$error = $@;
} else {
$attempt_result = eval { $self->attempt_code->(@_) };
$error = $@;
}
my $h = { action_retry => $self,
attempt_result => ( $wantarray ? \@attempt_result : $attempt_result ),
attempt_parameters => \@_,
};
$self->retry_if_code->($error, $h )
or $self->strategy->reset, $@ = $error, return ( $wantarray ? @attempt_result : $attempt_result );
if (! $self->strategy->needs_to_retry) {
$self->strategy->reset;
$self->has_on_failure_code
and return $self->on_failure_code->($error, $h);
return;
}
if ($self->non_blocking) {
my ($seconds, $microseconds) = gettimeofday;
$self->_needs_sleeping_until($seconds * 1000 + int($microseconds / 1000) + $self->strategy->compute_sleep_time);
} else {
usleep($self->strategy->compute_sleep_time * 1000);
$self->strategy->next_step;
}
}
}
lib/Action/Retry.pm view on Meta::CPAN
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
}
=head1 ATTRIBUTES
=head2 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 C<run()>
=head2 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:
=over
=item *
as first argument, a scalar which is the value of any exception that were
raised by the C<attempt_code>. Otherwise, undef.
=item *
as second argument, a HashRef, which contains these keys:
=over
=item action_retry
it's a reference on the ActionRetry instance. That way you can have access to
the strategy and other attributes.
=item attempt_result
It's a scalar, which is the result of C<attempt_code>. If C<attempt_code>
returned a list, then the scalar is the reference on this list.
=item attempt_parameters
It's the reference on the parameters that were given to C<attempt_code>.
=back
=back
C<retry_if_code> return value will be interpreted as a boolean : true return
value means the execution of C<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);
=head2 on_failure_code
ro, CodeRef, optional
If given, will be executed when retries are given up.
It will be given the same arguments as C<retry_if_code>. See C<retry_if_code> for their descriptions
=head2 strategy
( run in 1.261 second using v1.01-cache-2.11-cpan-d80b1682f3f )