File Coverage

blib/lib/Action/CircuitBreaker.pm
Criterion Covered Total %
statement 41 45 91.1
branch 15 18 83.3
condition n/a
subroutine 6 6 100.0
pod 1 1 100.0
total 63 70 90.0


line stmt bran cond sub pod time code
1             package Action::CircuitBreaker;
2             $Action::CircuitBreaker::VERSION = '0.1';
3             # ABSTRACT: Module to try to perform an action, with an option to suspend execution after a number of failures.
4              
5 2     2   1018 use Scalar::Util qw(blessed);
  2         16  
  2         296  
6 2     2   1239 use Time::HiRes qw(gettimeofday);
  2         3304  
  2         10  
7 2     2   391 use Carp;
  2         6  
  2         206  
8              
9 2     2   15 use base 'Exporter';
  2         6  
  2         406  
10             our @EXPORT = ((caller())[1] eq '-e' ? @EXPORT_OK : ());
11              
12 2     2   1260 use Moo;
  2         30186  
  2         16  
13              
14              
15              
16             has error_if_code => (
17             is => 'ro',
18             required => 1,
19             isa => sub { ref $_[0] eq 'CODE' },
20             default => sub { sub { $_[0] }; },
21             );
22              
23              
24             has on_failure_code => (
25             is => 'ro',
26             isa => sub { ref $_[0] eq 'CODE' },
27             predicate => 1,
28             );
29              
30              
31             has on_circuit_open => (
32             is => 'ro',
33             isa => sub { ref $_[0] eq 'CODE' },
34             predicate => 1,
35             );
36              
37              
38             has on_circuit_close => (
39             is => 'ro',
40             isa => sub { ref $_[0] eq 'CODE' },
41             predicate => 1,
42             );
43              
44              
45             has max_retries_number => (
46             is => 'ro',
47             lazy => 1,
48             default => sub { 10 },
49             );
50              
51             # the current number of retries
52             has _current_retries_number => (
53             is => 'rw',
54             lazy => 1,
55             default => sub { 0 },
56             init_arg => undef,
57             clearer => 1,
58             );
59              
60              
61             has open_time => (
62             is => 'ro',
63             lazy => 1,
64             default => sub { 10 },
65             );
66              
67             # Timestamp at which the circuit is available again
68             has _circuit_open_until => (
69             is => 'rw',
70             default => sub { 0 },
71             init_arg => undef,
72             );
73              
74              
75             sub run {
76 46     46 1 12036246 my ($self, $attempt_code) = @_;
77              
78 46 100       180 if (my $timestamp = $self->_circuit_open_until) {
79             # we can't execute until the timestamp has done
80 6         29 my ($seconds, $microseconds) = gettimeofday;
81 6 100       73 $seconds * 1000 + int($microseconds / 1000) >= $timestamp
82             or die 'The circuit is open and cannot be executed.';
83 1         19 $self->_circuit_open_until(0);
84 1 50       16 $self->has_on_circuit_close
85             and $self->on_circuit_close->();
86             }
87              
88 41         122 my $error;
89             my @attempt_result;
90 41         0 my $attempt_result;
91 41         0 my $wantarray;
92            
93 41 50       127 if (wantarray) {
    100          
94 0         0 $wantarray = 1;
95 0         0 @attempt_result = eval { $attempt_code->(@_) };
  0         0  
96 0         0 $error = $@;
97             } elsif ( ! defined wantarray ) {
98 30         34 eval { $attempt_code->(@_) };
  30         54  
99 30         149 $error = $@;
100             } else {
101 11         31 $attempt_result = eval { $attempt_code->(@_) };
  11         46  
102 11         135 $error = $@;
103             }
104              
105 41 50       179 my $h = { action_retry => $self,
106             attempt_result => ( $wantarray ? \@attempt_result : $attempt_result ),
107             attempt_parameters => \@_,
108             };
109              
110              
111 41 100       128 if ($self->error_if_code->($error, $h)) {
112 39         698 $self->_current_retries_number($self->_current_retries_number + 1);
113 39 100       803 if ($self->_current_retries_number >= $self->max_retries_number) {
114 4         108 my ($seconds, $microseconds) = gettimeofday;
115 4         70 my $open_until = ($self->open_time * 1000) + ($seconds * 1000 + int($microseconds / 1000));
116 4         18 $self->_circuit_open_until($open_until);
117 4 100       19 $self->has_on_circuit_open
118             and $self->on_circuit_open->();
119             }
120 39         868 die $error;
121             } else {
122 2         14 return $h->{attempt_result};
123             }
124             }
125              
126              
127             1;
128              
129             __END__