File Coverage

blib/lib/Perinci/Sub/Property/retry.pm
Criterion Covered Total %
statement 55 58 94.8
branch 17 24 70.8
condition 8 13 61.5
subroutine 5 5 100.0
pod n/a
total 85 100 85.0


line stmt bran cond sub pod time code
1             package Perinci::Sub::Property::retry;
2              
3 1     1   16345 use 5.010001;
  1         3  
4 1     1   3 use strict;
  1         1  
  1         19  
5 1     1   3 use warnings;
  1         1  
  1         27  
6              
7 1     1   414 use Perinci::Sub::PropertyUtil qw(declare_property);
  1         775  
  1         469  
8              
9             our $VERSION = '0.10'; # VERSION
10              
11             declare_property(
12             name => 'retry',
13             type => 'function',
14             schema => ['any' => {default=>0, of=>[
15             ['int' => {min=>0, default=>0}],
16             ['hash*' => {keys=>{
17             'n' => ['int' => {min=>0, default=>0}],
18             'delay' => ['int' => {min=>0, default=>0}], # XXX: use duration?
19             'success_statuses' => ['regex' => {default=>'^(2..|304)$'}],
20             'fatal_statuses' => 'regex',
21             'non_fatal_statuses' => 'regex',
22             'fatal_messages' => 'regex',
23             'non_fatal_messages' => 'regex',
24             }}],
25             ]}],
26             wrapper => {
27             meta => {
28             v => 2,
29             # very high, we want to trap errors as early as possible after eval,
30             # so we can retry it.
31             prio => 0,
32             convert => 1,
33             },
34             handler => sub {
35 22     22   2146169 my ($self, %args) = @_;
36              
37 22   33     81 my $v = $args{new} // $args{value};
38 22 100       75 $v = {n=>$v} unless ref($v) eq 'HASH';
39 22   50     56 $v->{n} //= 0;
40 22   100     106 $v->{delay} //= 0;
41 22   66     103 $v->{success_statuses} //= qr/^(2..|304)$/;
42              
43 22         43 for my $k (qw/success_statuses
44             fatal_statuses non_fatal_statuses
45             fatal_messages non_fatal_messages/) {
46 110 50 66     287 if (defined($v->{$k}) && ref($v->{$k}) ne 'Regexp') {
47 0         0 $v->{$k} = qr/$v->{$k}/;
48             }
49             }
50              
51 22 50       52 return unless $v->{n} > 0;
52              
53 22         50 $self->select_section('before_eval');
54 22         176 $self->push_lines(
55             '', 'my $_w_retries = 0;',
56             'RETRY: while (1) {');
57 22         429 $self->indent;
58              
59             # pass special variable for function to let it know about retries
60 22         108 $self->select_section('before_call_arg_validation');
61 22         128 my $args_as = $self->{_meta}{args_as};
62 22 50       55 if ($args_as eq 'hash') {
    0          
63 22         42 $self->push_lines('$args{-retries} = $_w_retries;');
64             } elsif ($args_as eq 'hashref') {
65 0         0 $self->push_lines('$args->{-retries} = $_w_retries;');
66             }
67              
68 22         330 $self->select_section('after_eval');
69 22 50       160 if ($self->{_arg}{meta}{result_naked}) {
70 0         0 $self->push_lines('if ($_w_eval_err) {');
71             } else {
72             $self->push_lines('if ($_w_eval_err || $_w_res->[0] !~ qr/'.
73 22         66 $v->{success_statuses}.'/) {');
74             }
75 22         322 $self->indent;
76 22 100       111 if ($v->{fatal_statuses}) {
77             $self->_errif('521', '"Can\'t retry (fatal status $_w_res->[0])"',
78 2         7 '$_w_res->[0] =~ qr/'.$v->{fatal_statuses}.'/');
79             }
80 22 100       109 if ($v->{non_fatal_statuses}) {
81             $self->_errif(
82             '521', '"Can\'t retry (not non-fatal status $_w_res->[0])"',
83 4         14 '$_w_res->[0] !~ qr/'.$v->{non_fatal_statuses}.'/');
84             }
85 22 100       177 if ($v->{fatal_messages}) {
86             $self->_errif(
87             '521', '"Can\'t retry (fatal message: $_w_res->[1])"',
88 2         7 '$_w_res->[1] =~ qr/'.$v->{fatal_messages}.'/');
89             }
90 22 100       110 if ($v->{non_fatal_messages}) {
91             $self->_errif(
92             '521', '"Can\'t retry (not non-fatal message $_w_res->[1])"',
93 4         17 '$_w_res->[1] !~ qr/'.$v->{non_fatal_messages}.'/');
94             }
95             $self->_errif('521', '"Maximum retries reached"',
96 22         240 '++$_w_retries > '.$v->{n});
97             $self->push_lines('sleep '.int($v->{delay}).';')
98 22 100       699 if $v->{delay};
99 22         44 $self->push_lines('next RETRY;');
100 22         154 $self->unindent;
101 22         88 $self->push_lines('} else {');
102 22         153 $self->indent;
103             # return information on number of retries performed
104 22 50       98 unless ($self->{_meta}{result_naked}) {
105 22         30 $self->push_lines('if ($_w_retries) {');
106 22         172 $self->push_lines($self->{_args}{indent} . '$_w_res->[3] //= {};');
107 22         167 $self->push_lines($self->{_args}{indent} . '$_w_res->[3]{retries}' .
108             ' = $_w_retries;');
109 22         150 $self->push_lines('}');
110             }
111 22         157 $self->push_lines('last RETRY;');
112 22         158 $self->unindent;
113 22         88 $self->push_lines('}');
114 22         147 $self->unindent;
115 22         94 $self->push_lines('', '# RETRY', '}', '');
116             },
117             },
118             );
119              
120             1;
121             # ABSTRACT: Specify automatic retry
122              
123             __END__
124              
125             =pod
126              
127             =encoding UTF-8
128              
129             =head1 NAME
130              
131             Perinci::Sub::Property::retry - Specify automatic retry
132              
133             =head1 VERSION
134              
135             This document describes version 0.10 of Perinci::Sub::Property::retry (from Perl distribution Perinci-Sub-Property-retry), released on 2016-05-11.
136              
137             =head1 SYNOPSIS
138              
139             # in function metadata
140             retry => 3,
141              
142             # more detailed
143             retry => {n=>3, delay=>10, success_statuses=>/^(2..|3..)$/},
144              
145             =head1 DESCRIPTION
146              
147             This property specifies retry behavior.
148              
149             Values: a hash containing these keys:
150              
151             =over 4
152              
153             =item * n => INT (default: 0)
154              
155             Number of retries, default is 0 which means no retry.
156              
157             =item * delay => INT (default: 0)
158              
159             Number of seconds to wait before each retry, default is 0 which means no wait
160             between retries.
161              
162             =item * success_statuses => REGEX (default: '^(2..|304)$')
163              
164             Which status is considered success.
165              
166             =item * fatal_statuses => REGEX
167              
168             If set, specify that status matching this should be considered fatal and no
169             retry should be attempted.
170              
171             =item * non_fatal_statuses => REGEX
172              
173             If set, specify that status I<not> matching this should be considered fatal and
174             no retry should be attempted.
175              
176             =item * fatal_messages => REGEX
177              
178             If set, specify that message matching this should be considered fatal and no
179             retry should be attempted.
180              
181             =item * non_fatal_messages => REGEX
182              
183             If set, specify that message I<not> matching this should be considered fatal and
184             no retry should be attempted.
185              
186             =back
187              
188             Property value can also be an integer (specifying just 'n').
189              
190             If function does not return enveloped result (result_naked=0), which means there
191             is no status returned, a function is assumed to fail only when it dies.
192              
193             This property's wrapper implementation currently uses a simple loop around
194             the eval block.
195              
196             It also pass a special argument to the function C<-retries> so that function can
197             be aware about retries.
198              
199             =head1 HOMEPAGE
200              
201             Please visit the project's homepage at L<https://metacpan.org/release/Perinci-Sub-Property-retry>.
202              
203             =head1 SOURCE
204              
205             Source repository is at L<https://github.com/perlancar/perl-Perinci-Sub-Property-retry>.
206              
207             =head1 BUGS
208              
209             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-Sub-Property-retry>
210              
211             When submitting a bug or request, please include a test-file or a
212             patch to an existing test-file that illustrates the bug or desired
213             feature.
214              
215             =head1 SEE ALSO
216              
217             L<Perinci>
218              
219             =head1 AUTHOR
220              
221             perlancar <perlancar@cpan.org>
222              
223             =head1 COPYRIGHT AND LICENSE
224              
225             This software is copyright (c) 2016 by perlancar@cpan.org.
226              
227             This is free software; you can redistribute it and/or modify it under
228             the same terms as the Perl 5 programming language system itself.
229              
230             =cut