File Coverage

blib/lib/Try/Tiny/Retry.pm
Criterion Covered Total %
statement 76 78 97.4
branch 32 34 94.1
condition 3 3 100.0
subroutine 17 17 100.0
pod 5 5 100.0
total 133 137 97.0


line stmt bran cond sub pod time code
1 5     5   211433 use 5.006;
  5         22  
  5         265  
2 5     5   33 use strict;
  5         12  
  5         388  
3 5     5   38 use warnings;
  5         233  
  5         391  
4              
5             package Try::Tiny::Retry;
6             # ABSTRACT: Extends Try::Tiny to allow retries
7             our $VERSION = '0.004'; # VERSION
8              
9 5     5   4871 use parent 'Exporter';
  5         2240  
  5         27  
10             our @EXPORT = qw/retry retry_if on_retry try catch finally/;
11             our @EXPORT_OK = ( @EXPORT, qw/delay delay_exp/ );
12             our %EXPORT_TAGS = ( all => [@EXPORT_OK] );
13              
14 5     5   747 use Carp;
  5         11  
  5         4579  
15             $Carp::Internal{ +__PACKAGE__ }++;
16              
17 5     5   14190 use Try::Tiny;
  5         9307  
  5         401  
18              
19             BEGIN {
20 10     10   22 eval "use Sub::Name; 1" or *{subname} = sub { 1 }
21 5 50   5   281 }
  5     5   6214  
  0         0  
  0         0  
22              
23             our $_DEFAULT_DELAY = 1e5; # to override for testing
24              
25             sub delay(&;@) { ## no critic
26 11     11 1 4338 my ( $block, @rest ) = @_;
27 11         86 return ( bless( \$block, 'Try::Tiny::Retry::Delay' ), @rest, );
28             }
29              
30             sub on_retry(&;@) { ## no critic
31 2     2 1 4284 my ( $block, @rest ) = @_;
32 2         17 return ( bless( \$block, 'Try::Tiny::Retry::OnRetry' ), @rest, );
33             }
34              
35             sub retry_if(&;@) { ## no critic
36 3     3 1 3809 my ( $block, @rest ) = @_;
37 3         24 return ( bless( \$block, 'Try::Tiny::Retry::RetryIf' ), @rest, );
38             }
39              
40             sub delay_exp(&;@) { ## no critic
41 9     9 1 995 my ( $params, @rest ) = @_;
42 9         23 my ( $n, $scale ) = $params->();
43              
44 9         5936 require Time::HiRes;
45              
46             return delay {
47 25 100   25   101 return if $_[0] >= $n;
48 23         6789 Time::HiRes::usleep( int rand( $scale * ( 1 << ( $_[0] - 1 ) ) ) );
49 9         8575 }, @rest;
50             }
51              
52             sub retry(&;@) { ## no critic
53 10     10 1 2091 my ( $try, @code_refs ) = @_;
54              
55             # name the block if we have Sub::Name
56 10         164 my $caller = caller;
57 10         50 subname( "${caller}::retry {...} " => $try );
58              
59             # we need to save this here to ensure retry block is evaluated correctly
60 10         21 my $wantarray = wantarray;
61              
62             # find labeled blocks in the argument list: retry_if and delay tag by blessing
63             # a scalar reference to the code block reference
64 10         17 my ( $delay, $on_retry, @conditions, @rest );
65              
66 10         24 foreach my $code_ref (@code_refs) {
67 16 100       81 if ( ref($code_ref) eq 'Try::Tiny::Retry::RetryIf' ) {
    100          
    100          
68 3         8 push @conditions, $$code_ref;
69             }
70             elsif ( ref($code_ref) eq 'Try::Tiny::Retry::OnRetry' ) {
71 2 50       41 croak 'A retry() may not be followed by multiple on_retry blocks'
72             if $on_retry;
73 2         5 $on_retry = $$code_ref;
74             }
75             elsif ( ref($code_ref) eq 'Try::Tiny::Retry::Delay' ) {
76 4 100       240 croak 'A retry() may not be followed by multiple delay blocks'
77             if $delay;
78 3         5 $delay = $$code_ref;
79             }
80             else {
81 7         32 push @rest, $code_ref;
82             }
83             }
84              
85             # default retry 10 times with default exponential backoff
86 9 100       36 if ( !defined $delay ) {
87 7     7   43 my ($code_ref) = delay_exp { 10, $_DEFAULT_DELAY };
  7         22  
88 7         53 $delay = $$code_ref;
89             }
90              
91             # execute code block and retry as necessary
92 9         15 my @ret;
93             my $retry = sub {
94 9     9   247 my $count = 0;
95 34         47 RETRY: {
96 9         13 $count++;
97 34         172 my ( $redo, $err );
98             try {
99             # evaluate the try block in the correct context
100 34 100       1271 if ($wantarray) {
    100          
101 1         4 @ret = $try->();
102             }
103             elsif ( defined $wantarray ) {
104 1         4 $ret[0] = $try->();
105             }
106             else {
107 32         85 $try->();
108             }
109             }
110             catch {
111 31         26209 $err = $_;
112             # if there are conditions, rethrow unless at least one is met
113 31 100       105 if (@conditions) {
114 10         14 my $met = 0;
115 10         17 for my $c (@conditions) {
116 10         14 local $_ = $err; # protect from modification
117 10 100       23 $met++ if $c->($count);
118             }
119 10 100       90 die $err unless $met;
120             }
121             # rethow if delay function signals stop with undef
122 28 100       68 die $err unless defined $delay->($count);
123             # if here, then we want to try again
124 25         124 $redo++;
125 34         297 };
126 28 100 100     1202 if ( defined $on_retry && $redo ) {
127 14         25 local $_ = $err;
128 14         44 $on_retry->($count);
129             }
130              
131 28 100       133 redo RETRY if $redo;
132             }
133 3 100       17 return $wantarray ? @ret : $ret[0];
134 9         46 };
135              
136             # call "&try" to bypass the prototype check
137 9         116 return &try( $retry, @rest );
138             }
139              
140             1;
141              
142              
143             # vim: ts=4 sts=4 sw=4 et:
144              
145             __END__