File Coverage

blib/lib/Action/Retry.pm
Criterion Covered Total %
statement 52 54 96.3
branch 20 22 90.9
condition n/a
subroutine 8 8 100.0
pod 2 2 100.0
total 82 86 95.3


line stmt bran cond sub pod time code
1             #
2             # This file is part of Action-Retry
3             #
4             # This software is copyright (c) 2013 by Damien "dams" Krotkine.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9             package Action::Retry;
10             {
11             $Action::Retry::VERSION = '0.24';
12             }
13              
14             # ABSTRACT: Module to try to perform an action, with various ways of retrying and sleeping between retries.
15              
16 5     5   16640 use Module::Runtime qw(use_module);
  5         13273  
  5         32  
17 5     5   266 use Scalar::Util qw(blessed);
  5         11  
  5         996  
18 5     5   6470 use Time::HiRes qw(usleep gettimeofday);
  5         12212  
  5         28  
19 5     5   10460 use Carp;
  5         13  
  5         471  
20              
21 5     5   25 use base 'Exporter';
  5         11  
  5         808  
22             our @EXPORT_OK = qw(retry);
23             # export by default if run from command line
24             our @EXPORT = ((caller())[1] eq '-e' ? @EXPORT_OK : ());
25              
26 5     5   7510 use Moo;
  5         142634  
  5         35  
27              
28              
29              
30             has attempt_code => (
31             is => 'ro',
32             required => 1,
33             isa => sub { ref $_[0] eq 'CODE' },
34             );
35              
36              
37             has retry_if_code => (
38             is => 'ro',
39             required => 1,
40             isa => sub { ref $_[0] eq 'CODE' },
41             default => sub { sub { $_[0] }; },
42             );
43              
44              
45             has on_failure_code => (
46             is => 'ro',
47             isa => sub { ref $_[0] eq 'CODE' },
48             predicate => 1,
49             );
50              
51              
52             has strategy => (
53             is => 'ro',
54             default => sub { 'Constant' },
55             coerce => sub {
56             my $attr = $_[0];
57             blessed($attr)
58             and return $attr;
59             my $class_name = $attr;
60             my $constructor_params = {};
61             if (ref $attr eq 'HASH') {
62             $class_name = (keys %$attr)[0];
63             $constructor_params = $attr->{$class_name};
64             }
65             $class_name = $class_name =~ /^\+(.+)$/ ? $1 : "Action::Retry::Strategy::$class_name";
66             return use_module($class_name)->new($constructor_params);
67             },
68             isa => sub { $_[0]->does('Action::Retry::Strategy') or croak 'Should consume the Action::Retry::Strategy role' },
69             );
70              
71              
72             has non_blocking => (
73             is => 'ro',
74             default => sub { 0 },
75             );
76              
77             # For non blocking mode, store the timestamp after which we can retry
78             has _needs_sleeping_until => (
79             is => 'rw',
80             default => sub { 0 },
81             init_arg => undef,
82             );
83              
84              
85             sub run {
86 15     15 1 212818 my $self = shift;
87              
88 15         374 while(1) {
89              
90 111 100       15692 if (my $timestamp = $self->_needs_sleeping_until) {
91             # we can't retry until we have waited enough time
92 5         26 my ($seconds, $microseconds) = gettimeofday;
93 5 100       40 $seconds * 1000 + int($microseconds / 1000) >= $timestamp
94             or return;
95 1         10 $self->_needs_sleeping_until(0);
96 1         665 $self->strategy->next_step;
97             }
98              
99 107         346 my $error;
100             my @attempt_result;
101 0         0 my $attempt_result;
102 0         0 my $wantarray;
103            
104 107 100       592 if (wantarray) {
    100          
105 5         11 $wantarray = 1;
106 5         12 @attempt_result = eval { $self->attempt_code->(@_) };
  5         46  
107 5         74 $error = $@;
108             } elsif ( ! defined wantarray ) {
109 97         258 eval { $self->attempt_code->(@_) };
  97         723  
110 97         2047 $error = $@;
111             } else {
112 5         12 $attempt_result = eval { $self->attempt_code->(@_) };
  5         41  
113 5         81 $error = $@;
114             }
115              
116 107 100       767 my $h = { action_retry => $self,
117             attempt_result => ( $wantarray ? \@attempt_result : $attempt_result ),
118             attempt_parameters => \@_,
119             };
120              
121              
122 107 100       682 $self->retry_if_code->($error, $h )
    100          
123             or $self->strategy->reset, $@ = $error, return ( $wantarray ? @attempt_result : $attempt_result );
124              
125 105 100       9037 if (! $self->strategy->needs_to_retry) {
126 9         1206 $self->strategy->reset;
127 9 50       3754 $self->has_on_failure_code
128             and return $self->on_failure_code->($error, $h);
129 9         70 return;
130             }
131              
132 96 100       9018 if ($self->non_blocking) {
133 2         28 my ($seconds, $microseconds) = gettimeofday;
134 2         28 $self->_needs_sleeping_until($seconds * 1000 + int($microseconds / 1000) + $self->strategy->compute_sleep_time);
135             } else {
136 94         507 usleep($self->strategy->compute_sleep_time * 1000);
137 94         5541190 $self->strategy->next_step;
138             }
139             }
140             }
141              
142              
143             sub retry (&;@) {
144 4     4 1 9873 my $code = shift;
145 4 50       29 @_ % 2
146             and croak "arguments to retry must be a CodeRef, and an even number of key / values";
147 4         17 my %args = @_;
148 4         175 Action::Retry->new( attempt_code => $code, %args )->run();
149             }
150              
151              
152             1;
153              
154             __END__