AnyEvent-RetryTimer

 view release on metacpan or  search on metacpan

META.yml  view on Meta::CPAN

--- #YAML:1.0
name:               AnyEvent-RetryTimer
version:            0.1
abstract:           Retry timers for AnyEvent
author:
    - Robin Redeker <elmex@ta-sa.org>
license:            perl
distribution_type:  module
configure_requires:
    ExtUtils::MakeMaker:  0
build_requires:
    ExtUtils::MakeMaker:  0
requires:
    AnyEvent:       3.5

README  view on Meta::CPAN

NAME
    AnyEvent::RetryTimer - Retry timers for AnyEvent

VERSION
    0.1

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;
       });

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 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!

METHODS
    my $timer = AnyEvent::RetryTimer->new (%args)
        This is the constructor, it constructs the object.

        At the end of the objects lifetime, when you get rid of the last
        reference to $timer, it will stop and running timeouts and not call
        any of the configured callbacks again.

        %args can contain these keys:

        on_retry => $retry_cb->($timer)
            $retry_cb is the callback that will be called for (re)tries.

            When this constructor is called and no "no_first_try" is given,
            an initial retry interval of the length 0 is started, which
            counts as the first try.

            Later it is also called after a retry interval has passed, which
            was initiated by a call to the "retry" method.

            The first argument is the $timer object itself.

        no_first_try => $bool
            This parameter defines whether the $retry_cb will be called when
            the AnyEvent::RetryTimer object is created or not. If $bool is
            true $retry_cb will not be called.

            The default is false.

        backoff => 'exponential'
            This is the back off algorithm that is used. Currently only

README  view on Meta::CPAN

            first call to "retry" and the finishing call to "success".

            If the number of retries is exceeded by a call to "retry" the
            "on_max_retries" callback is called (see below).

            Please note that a call to "success" will of course reset the
            internal count of calls to "retry".

            Default for this option is 0 (disabled).

        on_max_retries => $max_retry_cb->($timer)
            After "max_retries" the $max_retry_cb callback will be called
            with the $timer as first argument.

            It is usually called when a call to "retry" would exceed
            "max_retries".

        And then there are keys that are specific to the "backoff" method
        used:

        exponential

            start_interval => $secs

README  view on Meta::CPAN

                multiplied with $float and used for the next interval.

                Default is 1.5.

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

                Default is "3600 * 4", which is 4 hours.

    $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 "retry"), the call will be a nop.

        That means you can call "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 "current_interval" method.

    $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 $timer object after a call to "success".

    my $secs = $timer->current_interval
        Returns the length of the current interval to the next call to the
        $retry_cb.

AUTHOR
    Robin Redeker, "<elmex@ta-sa.org>"

SEE ALSO
    AnyEvent

COPYRIGHT & LICENSE

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)

This is the constructor, it constructs the object.

At the end of the objects lifetime, when you get rid of the last reference to
C<$timer>, it will stop and running timeouts and not call any of the configured
callbacks again.

C<%args> can contain these keys:

=over 4

=item on_retry => $retry_cb->($timer)

C<$retry_cb> is the callback that will be called for (re)tries.

When this constructor is called and no C<no_first_try> is given,
an initial retry interval of the length 0 is started, which counts as the
first try.

Later it is also called after a retry interval has passed, which was initiated
by a call to the C<retry> method.

The first argument is the C<$timer> object itself.

=item no_first_try => $bool

This parameter defines whether the C<$retry_cb> will be called when the
L<AnyEvent::RetryTimer> object is created or not. If C<$bool> is true
C<$retry_cb> will not be called.

The default is false.

=item backoff => 'exponential'

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

call to C<success>.

If the number of retries is exceeded by a call to C<retry>
the C<on_max_retries> callback is called (see below).

Please note that a call to C<success> will of course reset the internal count
of calls to C<retry>.

Default for this option is C<0> (disabled).

=item on_max_retries => $max_retry_cb->($timer)

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

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

      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})

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

         $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

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

   # specialcase: first call
   return 0 if $self->{timer} && not $self->{r};

   if ($self->{backoff} eq 'exponential') {
      return unless $self->{r};
      return $self->{r}->[0];
   }

   undef
}

=back

t/retry.t  view on Meta::CPAN

my $end = AE::cv;
my @intv;
my $retr_cnt = 0;
my $cur_ret;

my $tmr = AnyEvent::RetryTimer->new (
   on_retry =>sub {
      my ($tmr) = @_;

      $retr_cnt++;
      $cur_ret = AE::timer 0.1, 0, sub {
         $tmr->retry;
         push @intv, $tmr->current_interval;
      };
   },
   on_max_retries => sub {
      my ($tmr) = @_;
      $end->send;
   },
   max_retries    => 4,
   start_interval => 0.2,



( run in 1.569 second using v1.01-cache-2.11-cpan-49f99fa48dc )