File Coverage

blib/lib/Retry/Backoff.pm
Criterion Covered Total %
statement 72 76 94.7
branch 25 30 83.3
condition 4 6 66.6
subroutine 9 9 100.0
pod 1 3 33.3
total 111 124 89.5


line stmt bran cond sub pod time code
1             package Retry::Backoff;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-08-06'; # DATE
5             our $DIST = 'Retry-Backoff'; # DIST
6             our $VERSION = '0.003'; # VERSION
7              
8 1     1   75239 use 5.010001;
  1         11  
9 1     1   16 use strict 'subs', 'vars';
  1         2  
  1         33  
10 1     1   5 use warnings;
  1         2  
  1         43  
11 1     1   1753 use Log::ger;
  1         52  
  1         4  
12              
13 1     1   786 use Time::HiRes qw(time);
  1         1470  
  1         4  
14              
15 1     1   211 use Exporter 'import';
  1         2  
  1         658  
16             our @EXPORT_OK = qw(retry);
17              
18             sub new {
19 9     9 0 16 my $class = shift;
20 9         20 my %args = @_;
21              
22 9         17 my $self = {};
23              
24 9         21 $self->{strategy} = delete $args{strategy};
25 9 100       21 unless ($self->{strategy}) {
26 8         15 $self->{strategy} = 'Exponential';
27 8   50     17 $args{initial_delay} //= 1;
28 8   100     32 $args{max_attempts} //= 10;
29 8   50     26 $args{max_delay} //= 300;
30             }
31 9         33 $self->{on_failure} = delete $args{on_failure};
32 9         17 $self->{on_success} = delete $args{on_success};
33 9         17 $self->{retry_if} = delete $args{retry_if};
34 9         14 $self->{non_blocking} = delete $args{non_blocking};
35 9         13 $self->{attempt_code} = delete $args{attempt_code};
36              
37 9         23 my $ba_mod = "Algorithm::Backoff::$self->{strategy}";
38 9         50 (my $ba_mod_pm = "$ba_mod.pm") =~ s!::!/!g;
39 9         1167 require $ba_mod_pm;
40 9         3457 $self->{_backoff} = $ba_mod->new(%args);
41              
42 9         601 bless $self, $class;
43             }
44              
45             sub run {
46 9     9 0 14 my $self = shift;
47              
48 9         17 my @attempt_result;
49             my $attempt_result;
50 9         14 my $wantarray = wantarray;
51              
52 9         14 while(1) {
53 16 50       47 if (my $timestamp = $self->{_needs_sleeping_until}) {
54             # we can't retry until we have waited enough time
55 0         0 my $now = time();
56 0 0       0 $now >= $timestamp or return;
57 0         0 $self->{_needs_sleeping_until} = 0;
58             }
59              
60             # run the code, capture the error
61 16         27 my $error;
62 16 100       43 if ($wantarray) {
    100          
63 3         6 $wantarray = 1;
64 3         5 @attempt_result = eval { $self->{attempt_code}->(@_) };
  3         9  
65 3         29 $error = $@;
66             } elsif (!defined $wantarray) {
67 10         17 eval { $self->{attempt_code}->(@_) };
  10         28  
68 10         80 $error = $@;
69             } else {
70 3         7 $attempt_result = eval { $self->{attempt_code}->(@_) };
  3         8  
71 3         43 $error = $@;
72             }
73              
74 16 100       98 my $h = {
75             error => $error,
76             action_retry => $self,
77             attempt_result =>
78             ( $wantarray ? \@attempt_result : $attempt_result ),
79             attempt_parameters => \@_,
80             };
81              
82 16 100       38 if ($self->{retry_if}) {
83 2         6 $error = $self->{retry_if}->($h);
84             }
85              
86 16         28 my $delay;
87 16         40 my $now = time();
88 16 100       28 if ($error) {
89 9 100       19 $self->{on_failure}->($h) if $self->{on_failure};
90 9         32 $delay = $self->{_backoff}->failure($now);
91             } else {
92 7 100       16 $self->{on_success}->($h) if $self->{on_success};
93 7         28 $delay = $self->{_backoff}->success($now);
94             }
95              
96 16 100       522 if ($delay == -1) {
    50          
97 2         7 last;
98             } elsif ($self->{non_blocking}) {
99 0         0 $self->{_needs_sleeping_until} = $now + $delay;
100             } else {
101 14         1204 sleep $delay;
102             }
103              
104 14 100       104 last unless $error;
105             }
106              
107 9 100       51 return $wantarray ? @attempt_result : $attempt_result;
108             }
109              
110              
111             sub retry (&;@) {
112 9     9 1 26730 my $code = shift;
113 9 50       30 @_ % 2
114             and die "Arguments to retry must be a CodeRef, and an even number of key / values";
115 9         26 my %args = @_;
116 9         32 __PACKAGE__->new(attempt_code => $code, %args)->run();
117             }
118              
119             1;
120             # ABSTRACT: Retry a piece of code, with backoff strategies
121              
122             __END__