AnyEvent-Retry
view release on metacpan or search on metacpan
lib/AnyEvent/Retry.pm view on Meta::CPAN
package AnyEvent::Retry;
BEGIN {
$AnyEvent::Retry::VERSION = '0.03';
}
# ABSTRACT: try something until it works
use Moose;
use MooseX::Types::Common::Numeric qw(PositiveNum);
use AnyEvent::Retry::Types qw(Interval);
use AnyEvent;
use Try::Tiny;
use Scalar::Util qw(weaken);
use true;
use namespace::autoclean;
has 'after' => (
is => 'ro',
isa => PositiveNum,
default => 0,
);
has 'interval' => (
is => 'ro',
isa => Interval,
required => 1,
coerce => 1,
);
has '_sent_result' => (
accessor => '_sent_result',
isa => 'Bool',
default => 0,
);
has [qw/try on_failure on_success/] => (
is => 'ro',
isa => 'CodeRef',
required => 1,
);
has 'max_tries' => (
is => 'ro',
isa => PositiveNum,
default => 0,
);
has 'autostart' => (
is => 'ro',
isa => 'Bool',
default => 0,
);
has '_timer' => (
init_arg => undef,
writer => '_set_timer',
clearer => 'kill_timer',
predicate => 'has_timer',
);
sub BUILD {
my $self = shift;
$self->start if $self->autostart;
}
sub DEMOLISH {
my $self = shift;
$self->kill_timer;
if(!$self->_sent_result){
$self->_sent_result(1);
$self->on_failure->(demolish => 'DEMOLISH');
}
}
# set a timer to call handle_tick in the future
sub set_timer {
my ($self, $time, $i) = @_;
return $self->handle_tick($i) if $time <= 0;
weaken $self;
$self->_set_timer(
AnyEvent->timer( after => $time, cb => sub {
$self->kill_timer;
$self->handle_tick($i);
}),
);
return;
}
# called when the timer ticks; start the user's code running
sub handle_tick {
my ($self, $this_i) = @_;
$self->run_code;
}
# called when the user's code signals success or error
sub handle_result {
my ($self, $success, $status, $msg) = @_;
# if we hit these two cases, we are done forever
if($success){
$self->_sent_result(1);
$self->on_success->($msg);
return;
}
elsif($status =~ /error/){
$self->_sent_result(1);
$self->on_failure->( exception => $msg, $status );
return;
}
# no error, but not success (try again later)
my ($next_time, $next_i) = $self->interval->next;
if($self->max_tries > 0 && $next_i > $self->max_tries){
# done forever
$self->_sent_result(1);
$self->on_failure->( max_tries => $self->max_tries );
return;
}
# we didn't get the result this time, and we haven't exceeded
# $max_tries, so set the timer and do the whole thing again
$self->set_timer( $next_time, $next_i );
return;
}
# start the user's code running, with a continuation-passing-style
# callback to call when the result is ready
sub run_code {
my ($self) = @_;
# we weaken $self here so that if the user does "undef $retry", we
# DEMOLISH the object and silently discard the results of the
# running code. feel free to subclass if want to keep the class
# alive arbitrarily.
weaken $self;
my $success = sub {
my $result = shift;
return unless defined $self;
$self->handle_result(($result ? 1 : 0), 'success', $result);
return;
};
my $error = sub {
my $msg = shift;
return unless defined $self;
$self->handle_result(0, 'run error', $msg);
return;
};
try { $self->try->($success, $error) }
catch { $self->handle_result(0, 'startup error', $_) };
return;
}
# if the timer is running, stop it until resume is called
sub pause {
my $self = shift;
$self->kill_timer;
}
# fake a timer tick; run the user code, and set the timer to retry if
# necessary
sub resume {
my $self = shift;
$self->kill_timer; # just in case
$self->handle_tick(0);
}
# start the process. if the timer is running, die. if the timer is
# not running, start completely over.
sub start {
my $self = shift;
confess 'the job is already running' if $self->has_timer;
$self->interval->reset;
$self->_sent_result(0);
$self->set_timer( $self->after, 0 );
return;
}
__PACKAGE__->meta->make_immutable;
=pod
=head1 NAME
AnyEvent::Retry - try something until it works
=head1 VERSION
version 0.03
( run in 2.175 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )