AnyEvent-RetryTimer

 view release on metacpan or  search on metacpan

lib/AnyEvent/RetryTimer.pm  view on Meta::CPAN

package AnyEvent::RetryTimer;
use common::sense;
use Scalar::Util qw/weaken/;
use AnyEvent;

our $VERSION = '0.1';

=head1 NAME

AnyEvent::RetryTimer - Retry timers for AnyEvent

=head1 VERSION

0.1

=head1 SYNOPSIS

   use AnyEvent::RetryTimer;

   my $con =
      Something::Connection->new;

   my $timer;

   $con->on_disconnect (sub {
      $timer ||=
         AnyEvent::RetryTimer->new (
            on_retry => sub {
               $con->connect;
            });

      $timer->retry;

      my $secs = $timer->current_interval;

      warn "Lost connection, reconnecting in $secs seconds!";
   });

   $con->on_connect (sub {
      warn "Connected successfully!";

      $timer->success;
      undef $timer;
   });

=head1 DESCRIPTION

This is a small helper utility to manage timed retries.

This is a pattern I often stumble across when managing network connections.
And I'm tired to reimplement it again and again. So I wrote this module.

At the moment it only implements a simple exponential back off retry mechanism
(with configurable multiplier) using L<AnyEvent> timers. If there are
other back off strategies you find useful you are free to send a
feature request or even better a patch!

=head1 METHODS

=over 4

=item my $timer = AnyEvent::RetryTimer->new (%args)

lib/AnyEvent/RetryTimer.pm  view on Meta::CPAN


After C<max_retries> the C<$max_retry_cb> callback will be
called with the C<$timer> as first argument.

It is usually called when a call to C<retry> would exceed
C<max_retries>.

=back

And then there are keys that are specific to the C<backoff>
method used:

=over 4

=item B<exponential>

=over 4

=item start_interval => $secs

This is the length of the first interval. Given in seconds.

Default is C<10>.

=item multiplier => $float

This is the multiplier for the retry intervals. Each time
a C<retry> is done the previous (if any) interval will be
multiplied with C<$float> and used for the next interval.

Default is C<1.5>.

=item max_interval => $max_interval_secs

As exponential back off intervals can increase quite a lot
you can give the maximum time to wait in C<$max_interval_secs>.

Default is C<3600 * 4>, which is 4 hours.

=back

=back

=cut

sub new {
   my $this  = shift;
   my $class = ref ($this) || $this;
   my $self  = {
      backoff        => 'exponential',
      multiplier     => 1.5,
      max_interval   => 3600 * 4, # 6 hours
      max_retries    => 0,        # infinite
      start_interval => 10,
      @_
   };
   bless $self, $class;

   my $rself = $self;

   weaken $self;

   $self->{timer} = AE::timer 0, 0, sub {
      delete $self->{timer};
      $self->{on_retry}->($self) if $self;
   };

   return $rself
}

=item $timer->retry

This method initiates or continues retries. If already a retry interval
is installed (eg. by the constructor or another previous unfinished call
to C<retry>), the call will be a nop.

That means you can call C<retry> directly after you created this object and
will not cause the initial try to be "retried".

If you are interested in the length of the current interval (after a
call to this method), you can call the C<current_interval> method.

=cut

sub retry {
   my ($self) = @_;

   weaken $self;

   return if $self->{timer};

   if ($self->{backoff} eq 'exponential') {
      my $r;

      # layout of $r = [$interval, $retry_cnt]
      if ($r = $self->{r}) {

         if ($self->{max_retries}
             && $self->{on_max_retries}
             && $r->[1] >= $self->{max_retries})
         {
            delete $self->{r};
            $self->{on_max_retries}->($self);
            return;
         }

         $r->[0] *= $self->{multiplier};
         $r->[0] =
            $r->[0] > $self->{max_interval}
               ? $self->{max_interval}
               : $r->[0];

      } else {
         $r = $self->{r} = [$self->{start_interval}];
      }

      $self->{timer} = AE::timer $r->[0], 0, sub {
         $r->[1]++;
         delete $self->{timer};
         $self->{on_retry}->($self)
            if $self && $self->{on_retry};
      };
   }
}

=item $timer->success

This signals that the last retry was successful and it will
reset any state or intervals to the initial settings given
to the constructor.

You can reuse the C<$timer> object after a call to C<success>.

=cut

sub success {
   my ($self) = @_;
   delete $self->{r}; # reset timer & wait counter
   delete $self->{timer};
}

=item my $secs = $timer->current_interval

Returns the length of the current interval to the
next call to the C<$retry_cb>.

=cut



( run in 2.792 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )