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 )