File Coverage

blib/lib/Sub/Throttler/Periodic/EV.pm
Criterion Covered Total %
statement 93 103 90.2
branch 27 36 75.0
condition 5 10 50.0
subroutine 20 21 95.2
pod 8 8 100.0
total 153 178 85.9


line stmt bran cond sub pod time code
1             package Sub::Throttler::Periodic::EV;
2 2     2   26833 use 5.010001;
  2         5  
  2         65  
3 2     2   8 use warnings;
  2         3  
  2         42  
4 2     2   9 use strict;
  2         2  
  2         48  
5 2     2   8 use utf8;
  2         2  
  2         11  
6 2     2   36 use Carp;
  2         2  
  2         182  
7             our @CARP_NOT = qw( Sub::Throttler );
8              
9             our $VERSION = 'v0.2.3';
10              
11 2     2   561 use parent qw( Sub::Throttler::Limit );
  2         293  
  2         9  
12 2     2   104 use Sub::Throttler qw( throttle_flush );
  2         4  
  2         7  
13 2     2   286170 use Time::HiRes qw( time sleep );
  2         1492  
  2         11  
14 2     2   335 use Scalar::Util qw( weaken );
  2         4  
  2         97  
15 2     2   1181 use EV;
  2         4017  
  2         79  
16              
17              
18             sub new {
19 2     2   10 use warnings FATAL => qw( misc );
  2         3  
  2         1697  
20 35     35 1 98552 my ($class, %opt) = @_;
21 34   100     482 my $self = bless {
      100        
      33        
22             limit => delete $opt{limit} // 1,
23             period => delete $opt{period} // 1,
24             acquired=> {}, # { $id => { $key => $quantity, … }, … }
25             used => {}, # { $key => $quantity, … }
26             }, ref $class || $class;
27 34 100       276 croak 'limit must be an unsigned integer' if $self->{limit} !~ /\A\d+\z/ms;
28 32 50       88 croak 'period must be a positive number' if $self->{period} <= 0;
29 32 100       95 croak 'bad param: '.(keys %opt)[0] if keys %opt;
30 30         122 weaken(my $this = $self);
31 30 50   24   303 $self->{_t} = EV::periodic 0, $self->{period}, 0, sub { $this && $this->_tick() };
  24         4219785  
32 30         172 $self->{_t}->keepalive(0);
33 30         70 return $self;
34             }
35              
36             sub acquire {
37 0     0 1 0 my ($self, $id, $key, $quantity) = @_;
38 0 0       0 if (!$self->try_acquire($id, $key, $quantity)) {
39 0 0 0     0 if ($quantity <= $self->{limit} && $self->{used}{$key}) {
40 0         0 my $time = time;
41 0         0 my $delay = int($time/$self->{period})*$self->{period} + $self->{period} - $time;
42 0         0 sleep $delay;
43 0         0 $self->_tick();
44             }
45 0 0       0 if (!$self->try_acquire($id, $key, $quantity)) {
46 0         0 croak "$self: unable to acquire $quantity of resource '$key'";
47             }
48             }
49 0         0 return $self;
50             }
51              
52             sub load {
53 5     5 1 103 my ($class, $state) = @_;
54 5 100       26 croak 'bad state: wrong algorithm' if $state->{algo} ne __PACKAGE__;
55 4         32 my $v = version->parse($state->{version});
56 4 100       122 if ($v > $VERSION) {
57 1         20 carp 'restoring state saved by future version';
58             }
59 4         358 my $self = $class->new(limit=>$state->{limit}, period=>$state->{period});
60             # time jump backward, no matter how much, handled like we still is in
61             # current period, to be safe
62 4 100       27 if (int($state->{at}/$self->{period})*$self->{period} + $self->{period} > time) {
63 3         4 $self->{used} = $state->{used};
64             }
65 4 100       5 if (keys %{ $self->{used} }) {
  4         10  
66 3         6 $self->{_t}->keepalive(1);
67             }
68 4         11 return $self;
69             }
70              
71             sub period {
72 8     8 1 34 my ($self, $period) = @_;
73 8 100       27 if (1 == @_) {
74 5         35 return $self->{period};
75             }
76 3 50       31 croak 'period must be a positive number' if $period <= 0;
77 3         7 $self->{period} = $period;
78 3         25 $self->{_t}->set(0, $self->{period}, 0);
79 3         8 return $self;
80             }
81              
82             sub release {
83 13     13 1 20203 my ($self, $id) = @_;
84 13 100       90 croak sprintf '%s not acquired anything', $id if !$self->{acquired}{$id};
85 10         158 delete $self->{acquired}{$id};
86 10         28 return $self;
87             }
88              
89             sub release_unused {
90 10     10 1 12526 my $self = shift->SUPER::release_unused(@_);
91 7 100       8 if (!keys %{ $self->{used} }) {
  7         28  
92 3         26 $self->{_t}->keepalive(0);
93             }
94 7         14 return $self;
95             }
96              
97             sub save {
98 2     2 1 3 my ($self) = @_;
99 2         34 my $state = {
100             algo => __PACKAGE__,
101             version => version->declare($VERSION)->numify,
102             limit => $self->{limit},
103             period => $self->{period},
104             used => $self->{used},
105             at => time,
106             };
107 2         9 return $state;
108             }
109              
110             sub try_acquire {
111 96     96 1 15458 my $self = shift;
112 96 100       344 if ($self->SUPER::try_acquire(@_)) {
113 54         173 $self->{_t}->keepalive(1);
114 54         164 return 1;
115             }
116 38         106 return;
117             }
118              
119             sub _tick {
120 24     24   56 my $self = shift;
121 24         48 for my $id (keys %{ $self->{acquired} }) {
  24         188  
122 22         31 for my $key (keys %{ $self->{acquired}{$id} }) {
  22         96  
123 38         159 $self->{acquired}{$id}{$key} = 0;
124             }
125             }
126             # OPTIMIZATION call throttle_flush() only if amount of available
127             # resources increased (i.e. if some sources was released)
128 24 100       62 if (keys %{ $self->{used} }) {
  24         116  
129 13         33 $self->{used} = {};
130 13         77 throttle_flush();
131             }
132 24 100       83 if (!keys %{ $self->{used} }) {
  24         146  
133 23         178 $self->{_t}->keepalive(0);
134             }
135 24         1704785 return;
136             }
137              
138              
139             1; # Magic true value required at end of module
140             __END__