| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sub::Throttler::Rate::AnyEvent; |
|
2
|
3
|
|
|
3
|
|
35558
|
use 5.010001; |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
95
|
|
|
3
|
3
|
|
|
3
|
|
11
|
use warnings; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
61
|
|
|
4
|
3
|
|
|
3
|
|
11
|
use strict; |
|
|
3
|
|
|
|
|
8
|
|
|
|
3
|
|
|
|
|
56
|
|
|
5
|
3
|
|
|
3
|
|
8
|
use utf8; |
|
|
3
|
|
|
|
|
2
|
|
|
|
3
|
|
|
|
|
13
|
|
|
6
|
3
|
|
|
3
|
|
38
|
use Carp; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
226
|
|
|
7
|
|
|
|
|
|
|
our @CARP_NOT = qw( Sub::Throttler ); |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = 'v0.2.3'; |
|
10
|
|
|
|
|
|
|
|
|
11
|
3
|
|
|
3
|
|
352
|
use parent qw( Sub::Throttler::algo ); |
|
|
3
|
|
|
|
|
279
|
|
|
|
3
|
|
|
|
|
11
|
|
|
12
|
3
|
|
|
3
|
|
168
|
use Sub::Throttler qw( throttle_flush ); |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
16
|
|
|
13
|
3
|
|
|
3
|
|
221
|
use Time::HiRes qw( clock_gettime CLOCK_MONOTONIC time sleep ); |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
18
|
|
|
14
|
3
|
|
|
3
|
|
446
|
use List::Util qw( min ); |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
137
|
|
|
15
|
3
|
|
|
3
|
|
10
|
use Scalar::Util qw( weaken ); |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
89
|
|
|
16
|
3
|
|
|
3
|
|
1855
|
use Storable qw( dclone ); |
|
|
3
|
|
|
|
|
7445
|
|
|
|
3
|
|
|
|
|
176
|
|
|
17
|
3
|
|
|
3
|
|
2573
|
use AnyEvent; |
|
|
3
|
|
|
|
|
11420
|
|
|
|
3
|
|
|
|
|
119
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub new { |
|
21
|
3
|
|
|
3
|
|
16
|
use warnings FATAL => qw( misc ); |
|
|
3
|
|
|
|
|
2
|
|
|
|
3
|
|
|
|
|
3579
|
|
|
22
|
9
|
|
|
9
|
1
|
103052
|
my ($class, %opt) = @_; |
|
23
|
9
|
|
100
|
|
|
135
|
my $self = bless { |
|
|
|
|
100
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
24
|
|
|
|
|
|
|
limit => delete $opt{limit} // 1, |
|
25
|
|
|
|
|
|
|
period => delete $opt{period} // 1, |
|
26
|
|
|
|
|
|
|
acquired=> {}, # { $id => { $key => [$time, $quantity], … }, … } |
|
27
|
|
|
|
|
|
|
used => {}, # { $key => { next => $idx, data => [ $time, … ] }, … } |
|
28
|
|
|
|
|
|
|
_cb => undef, # callback for timer |
|
29
|
|
|
|
|
|
|
_t => undef, # undef or AE::timer |
|
30
|
|
|
|
|
|
|
}, ref $class || $class; |
|
31
|
9
|
50
|
|
|
|
92
|
croak 'limit must be an unsigned integer' if $self->{limit} !~ /\A\d+\z/ms; |
|
32
|
9
|
50
|
|
|
|
37
|
croak 'period must be a positive number' if $self->{period} <= 0; |
|
33
|
9
|
50
|
|
|
|
39
|
croak 'period is too large' if $self->{period} >= -Sub::Throttler::Rate::rr::EMPTY(); |
|
34
|
9
|
50
|
|
|
|
26
|
croak 'bad param: '.(keys %opt)[0] if keys %opt; |
|
35
|
9
|
|
|
|
|
34
|
weaken(my $this = $self); |
|
36
|
9
|
0
|
|
0
|
|
40
|
$self->{_cb} = sub { $this && $this->_tick() }; |
|
|
0
|
|
|
|
|
0
|
|
|
37
|
9
|
|
|
|
|
59
|
return $self; |
|
38
|
|
|
|
|
|
|
} |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub acquire { |
|
41
|
11
|
|
|
11
|
1
|
16
|
my ($self, $id, $key, $quantity) = @_; |
|
42
|
11
|
50
|
|
|
|
22
|
if (!$self->try_acquire($id, $key, $quantity)) { |
|
43
|
11
|
50
|
|
|
|
36
|
if ($quantity <= $self->{limit}) { |
|
44
|
11
|
|
|
|
|
29
|
my $now = clock_gettime(CLOCK_MONOTONIC); |
|
45
|
11
|
|
|
|
|
53
|
my $delay = $self->{used}{$key}->get($quantity) + $self->{period} - $now; |
|
46
|
|
|
|
|
|
|
# resource may expire between try_acquire() and clock_gettime() |
|
47
|
11
|
50
|
|
|
|
28
|
if ($delay > 0) { |
|
48
|
11
|
|
|
|
|
1178429
|
sleep $delay; |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
} |
|
51
|
11
|
50
|
|
|
|
100
|
if (!$self->try_acquire($id, $key, $quantity)) { |
|
52
|
0
|
|
|
|
|
0
|
croak "$self: unable to acquire $quantity of resource '$key'"; |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
} |
|
55
|
11
|
|
|
|
|
40
|
return $self; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub limit { |
|
59
|
1
|
|
|
1
|
1
|
34
|
my ($self, $limit) = @_; |
|
60
|
1
|
50
|
|
|
|
4
|
if (1 == @_) { |
|
61
|
1
|
|
|
|
|
6
|
return $self->{limit}; |
|
62
|
|
|
|
|
|
|
} |
|
63
|
0
|
0
|
|
|
|
0
|
croak 'limit must be an unsigned integer' if $limit !~ /\A\d+\z/ms; |
|
64
|
|
|
|
|
|
|
# OPTIMIZATION call throttle_flush() only if amount of available |
|
65
|
|
|
|
|
|
|
# resources increased (i.e. limit was increased) |
|
66
|
0
|
|
|
|
|
0
|
my $resources_increases = $self->{limit} < $limit; |
|
67
|
0
|
|
|
|
|
0
|
$self->{limit} = $limit; |
|
68
|
0
|
|
|
|
|
0
|
for my $rr (values %{ $self->{used} }) { |
|
|
0
|
|
|
|
|
0
|
|
|
69
|
0
|
|
|
|
|
0
|
$rr->resize($self->{limit}); |
|
70
|
|
|
|
|
|
|
} |
|
71
|
0
|
0
|
|
|
|
0
|
if ($resources_increases) { |
|
72
|
0
|
|
|
|
|
0
|
throttle_flush(); |
|
73
|
|
|
|
|
|
|
} |
|
74
|
0
|
|
|
|
|
0
|
return $self; |
|
75
|
|
|
|
|
|
|
} |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
sub load { |
|
78
|
5
|
|
|
5
|
1
|
122
|
my ($class, $state) = @_; |
|
79
|
5
|
100
|
|
|
|
23
|
croak 'bad state: wrong algorithm' if $state->{algo} ne __PACKAGE__; |
|
80
|
4
|
|
|
|
|
44
|
my $v = version->parse($state->{version}); |
|
81
|
4
|
100
|
|
|
|
143
|
if ($v > $VERSION) { |
|
82
|
1
|
|
|
|
|
15
|
carp 'restoring state saved by future version'; |
|
83
|
|
|
|
|
|
|
} |
|
84
|
4
|
|
|
|
|
259
|
my $self = $class->new(limit=>$state->{limit}, period=>$state->{period}); |
|
85
|
4
|
|
|
|
|
96
|
$self->{used} = dclone($state->{used}); |
|
86
|
4
|
|
|
|
|
21
|
my ($time, $now) = (time, clock_gettime(CLOCK_MONOTONIC)); |
|
87
|
|
|
|
|
|
|
# time jump backward, no matter how much, handled like we still is in |
|
88
|
|
|
|
|
|
|
# current period, to be safe |
|
89
|
4
|
100
|
|
|
|
32
|
if ($state->{at} > $time) { |
|
90
|
2
|
|
|
|
|
3
|
$time = $state->{at}; |
|
91
|
|
|
|
|
|
|
} |
|
92
|
4
|
|
|
|
|
8
|
my $diff = $time - $now; |
|
93
|
4
|
|
|
|
|
6
|
for my $data (map {$_->{data}} values %{ $self->{used} }) { |
|
|
4
|
|
|
|
|
13
|
|
|
|
4
|
|
|
|
|
10
|
|
|
94
|
4
|
|
|
|
|
6
|
for (@{ $data }) { |
|
|
4
|
|
|
|
|
7
|
|
|
95
|
12
|
50
|
|
|
|
24
|
if ($_ != Sub::Throttler::Rate::rr::EMPTY()) { |
|
96
|
12
|
|
|
|
|
17
|
$_ -= $diff; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
} |
|
100
|
4
|
|
|
|
|
3
|
for (values %{ $self->{used} }) { |
|
|
4
|
|
|
|
|
7
|
|
|
101
|
4
|
|
|
|
|
11
|
bless $_, 'Sub::Throttler::Rate::rr'; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
4
|
|
|
|
|
19
|
$self->{_t} = AE::timer 0, 0, $self->{_cb}; |
|
104
|
4
|
|
|
|
|
15
|
return $self; |
|
105
|
|
|
|
|
|
|
} |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub period { |
|
108
|
1
|
|
|
1
|
1
|
2
|
my ($self, $period) = @_; |
|
109
|
1
|
50
|
|
|
|
6
|
if (1 == @_) { |
|
110
|
1
|
|
|
|
|
4
|
return $self->{period}; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
0
|
0
|
|
|
|
0
|
croak 'period must be a positive number' if $period <= 0; |
|
113
|
0
|
0
|
|
|
|
0
|
croak 'period is too large' if $self->{period} >= -Sub::Throttler::Rate::rr::EMPTY(); |
|
114
|
|
|
|
|
|
|
# OPTIMIZATION call throttle_flush() only if amount of available |
|
115
|
|
|
|
|
|
|
# resources increased (i.e. period was decreased) |
|
116
|
0
|
|
|
|
|
0
|
my $resources_increases = $self->{period} > $period; |
|
117
|
0
|
|
|
|
|
0
|
$self->{period} = $period; |
|
118
|
0
|
0
|
|
|
|
0
|
if ($resources_increases) { |
|
119
|
0
|
0
|
|
|
|
0
|
if ($self->{_t}) { |
|
120
|
0
|
|
|
|
|
0
|
$self->{_t} = undef; |
|
121
|
0
|
|
|
|
|
0
|
$self->_tick(); |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
else { |
|
124
|
0
|
|
|
|
|
0
|
throttle_flush(); |
|
125
|
|
|
|
|
|
|
} |
|
126
|
|
|
|
|
|
|
} |
|
127
|
0
|
|
|
|
|
0
|
return $self; |
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub release { |
|
131
|
18
|
|
|
18
|
1
|
25
|
my ($self, $id) = @_; |
|
132
|
18
|
50
|
|
|
|
51
|
croak sprintf '%s not acquired anything', $id if !$self->{acquired}{$id}; |
|
133
|
18
|
|
|
|
|
36
|
delete $self->{acquired}{$id}; |
|
134
|
18
|
|
|
|
|
44
|
return $self; |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub release_unused { |
|
138
|
13
|
|
|
13
|
1
|
21
|
my ($self, $id) = @_; |
|
139
|
13
|
50
|
|
|
|
50
|
croak sprintf '%s not acquired anything', $id if !$self->{acquired}{$id}; |
|
140
|
|
|
|
|
|
|
|
|
141
|
13
|
|
|
|
|
30
|
my $now = clock_gettime(CLOCK_MONOTONIC); |
|
142
|
13
|
|
|
|
|
47
|
for my $key (grep {$self->{used}{$_}} keys %{ $self->{acquired}{$id} }) { |
|
|
13
|
|
|
|
|
57
|
|
|
|
13
|
|
|
|
|
54
|
|
|
143
|
13
|
|
|
|
|
17
|
my ($time, $quantity) = @{ $self->{acquired}{$id}{$key} }; |
|
|
13
|
|
|
|
|
30
|
|
|
144
|
13
|
|
|
|
|
49
|
$self->{used}{$key}->del($time, $quantity); |
|
145
|
|
|
|
|
|
|
# clean up (avoid memory leak in long run with unique keys) |
|
146
|
13
|
100
|
|
|
|
54
|
if ($self->{used}{$key}->get($self->{limit}) + $self->{period} <= $now) { |
|
147
|
11
|
|
|
|
|
64
|
delete $self->{used}{$key}; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
} |
|
150
|
13
|
|
|
|
|
33
|
delete $self->{acquired}{$id}; |
|
151
|
13
|
|
|
|
|
44
|
throttle_flush(); |
|
152
|
13
|
100
|
|
|
|
14
|
if (!keys %{ $self->{used} }) { |
|
|
13
|
|
|
|
|
38
|
|
|
153
|
11
|
|
|
|
|
21
|
$self->{_t} = undef; |
|
154
|
|
|
|
|
|
|
} |
|
155
|
13
|
|
|
|
|
94
|
return $self; |
|
156
|
|
|
|
|
|
|
} |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
sub save { |
|
159
|
1
|
|
|
1
|
1
|
2
|
my ($self) = @_; |
|
160
|
1
|
|
|
|
|
7
|
my ($time, $now) = (time, clock_gettime(CLOCK_MONOTONIC)); |
|
161
|
1
|
|
|
|
|
28
|
my $diff = $time - $now; |
|
162
|
1
|
|
|
|
|
194
|
my $state = { |
|
163
|
|
|
|
|
|
|
algo => __PACKAGE__, |
|
164
|
|
|
|
|
|
|
version => version->declare($VERSION)->numify, |
|
165
|
|
|
|
|
|
|
limit => $self->{limit}, |
|
166
|
|
|
|
|
|
|
period => $self->{period}, |
|
167
|
|
|
|
|
|
|
used => dclone($self->{used}), |
|
168
|
|
|
|
|
|
|
at => $time, |
|
169
|
|
|
|
|
|
|
}; |
|
170
|
1
|
|
|
|
|
9
|
for my $data (map {$_->{data}} values %{ $state->{used} }) { |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
4
|
|
|
171
|
1
|
|
|
|
|
2
|
for (@{ $data }) { |
|
|
1
|
|
|
|
|
3
|
|
|
172
|
3
|
50
|
|
|
|
8
|
if ($_ != Sub::Throttler::Rate::rr::EMPTY()) { |
|
173
|
3
|
|
|
|
|
8
|
$_ += $diff; |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
} |
|
177
|
1
|
|
|
|
|
2
|
for (values %{ $state->{used} }) { |
|
|
1
|
|
|
|
|
4
|
|
|
178
|
1
|
|
|
|
|
1
|
$_ = {%{ $_ }}; # unbless |
|
|
1
|
|
|
|
|
15
|
|
|
179
|
|
|
|
|
|
|
} |
|
180
|
1
|
|
|
|
|
3
|
return $state; |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub try_acquire { |
|
184
|
62
|
|
|
62
|
1
|
218
|
my ($self, $id, $key, $quantity) = @_; |
|
185
|
62
|
100
|
66
|
|
|
247
|
croak sprintf '%s already acquired %s', $id, $key |
|
186
|
|
|
|
|
|
|
if $self->{acquired}{$id} && exists $self->{acquired}{$id}{$key}; |
|
187
|
61
|
50
|
|
|
|
117
|
croak 'quantity must be positive' if $quantity <= 0; |
|
188
|
|
|
|
|
|
|
|
|
189
|
61
|
|
|
|
|
192
|
my $now = clock_gettime(CLOCK_MONOTONIC); |
|
190
|
|
|
|
|
|
|
|
|
191
|
61
|
|
66
|
|
|
533
|
$self->{used}{$key} ||= Sub::Throttler::Rate::rr->new($self->{limit}); |
|
192
|
61
|
100
|
|
|
|
202
|
if (!$self->{used}{$key}->add($self->{period}, $now, $quantity)) { |
|
193
|
26
|
|
|
|
|
71
|
return; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
35
|
|
|
|
|
139
|
$self->{acquired}{$id}{$key} = [$now, $quantity]; |
|
197
|
35
|
100
|
|
|
|
121
|
if (!$self->{_t}) { |
|
198
|
16
|
|
|
|
|
181
|
$self->{_t} = AE::timer $self->{period}, 0, $self->{_cb}; |
|
199
|
|
|
|
|
|
|
} |
|
200
|
35
|
|
|
|
|
4373
|
return 1; |
|
201
|
|
|
|
|
|
|
} |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub _tick { |
|
204
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
205
|
0
|
|
|
|
|
0
|
my $now = clock_gettime(CLOCK_MONOTONIC); |
|
206
|
0
|
|
|
|
|
0
|
my $when = 0; |
|
207
|
0
|
|
|
|
|
0
|
for my $key (keys %{ $self->{used} }) { |
|
|
0
|
|
|
|
|
0
|
|
|
208
|
0
|
|
|
|
|
0
|
my $after = $self->{used}{$key}->after($now - $self->{period}); |
|
209
|
0
|
0
|
0
|
|
|
0
|
if (!$after) { |
|
|
|
0
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
0
|
delete $self->{used}{$key}; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
elsif (!$when || $when > $after) { |
|
213
|
0
|
|
|
|
|
0
|
$when = $after; |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
} |
|
216
|
0
|
0
|
|
|
|
0
|
$self->{_t} = !$when ? undef : AE::timer $when + $self->{period} - $now, 0, $self->{_cb}; |
|
217
|
0
|
|
|
|
|
0
|
throttle_flush(); |
|
218
|
0
|
|
|
|
|
0
|
return; |
|
219
|
|
|
|
|
|
|
} |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
package Sub::Throttler::Rate::rr; ## no critic (ProhibitMultiplePackages) |
|
223
|
3
|
|
|
3
|
|
50
|
use 5.010001; |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
78
|
|
|
224
|
3
|
|
|
3
|
|
9
|
use warnings; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
57
|
|
|
225
|
3
|
|
|
3
|
|
14
|
use strict; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
89
|
|
|
226
|
3
|
|
|
3
|
|
16
|
use utf8; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
18
|
|
|
227
|
3
|
|
|
3
|
|
51
|
use Carp; |
|
|
3
|
|
|
|
|
29
|
|
|
|
3
|
|
|
|
|
199
|
|
|
228
|
|
|
|
|
|
|
|
|
229
|
3
|
|
|
3
|
|
12
|
use constant EMPTY => -1_000_000_000; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
2542
|
|
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
sub new { |
|
233
|
16
|
|
|
16
|
|
26
|
my ($class, $len) = @_; |
|
234
|
16
|
|
33
|
|
|
165
|
my $self = bless { |
|
235
|
|
|
|
|
|
|
next => 0, |
|
236
|
|
|
|
|
|
|
data => [ (EMPTY) x $len ], |
|
237
|
|
|
|
|
|
|
}, ref $class || $class; |
|
238
|
16
|
|
|
|
|
63
|
return $self; |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub add { |
|
242
|
61
|
|
|
61
|
|
103
|
my ($self, $period, $time, $quantity) = @_; |
|
243
|
61
|
|
|
|
|
54
|
my $len = @{ $self->{data} }; |
|
|
61
|
|
|
|
|
109
|
|
|
244
|
|
|
|
|
|
|
# try_acquire() guarantee $quantity > 0, so we continue only if $len > 0 |
|
245
|
|
|
|
|
|
|
# (thus avoid division by zero on % $len) and there is a chance to add |
|
246
|
|
|
|
|
|
|
# $quantity elements |
|
247
|
61
|
100
|
|
|
|
118
|
if ($quantity > $len) { |
|
248
|
1
|
|
|
|
|
4
|
return; |
|
249
|
|
|
|
|
|
|
} |
|
250
|
60
|
|
|
|
|
130
|
my $required = ($self->{next} + $quantity - 1) % $len; |
|
251
|
|
|
|
|
|
|
# {data} is sorted, last added element ($self->{next}-1) is guaranteed |
|
252
|
|
|
|
|
|
|
# to be largest of all elements, so all elements between (inclusive) |
|
253
|
|
|
|
|
|
|
# $self->{next} and $required are guaranteed to be either EMPTY |
|
254
|
|
|
|
|
|
|
# or <= $self->{next}-1 element, and $required element is largest of them |
|
255
|
60
|
100
|
|
|
|
164
|
if ($self->{data}[$required] > $time - $period) { |
|
256
|
25
|
|
|
|
|
64
|
return; |
|
257
|
|
|
|
|
|
|
} |
|
258
|
35
|
|
|
|
|
84
|
for (1 .. $quantity) { |
|
259
|
38
|
|
|
|
|
60
|
$self->{data}[ $self->{next} ] = $time; |
|
260
|
38
|
|
|
|
|
104
|
($self->{next} += 1) %= $len; |
|
261
|
|
|
|
|
|
|
} |
|
262
|
35
|
|
|
|
|
106
|
return 1; |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# Return time of acquiring first resource after $time or nothing. |
|
266
|
|
|
|
|
|
|
sub after { |
|
267
|
0
|
|
|
0
|
|
0
|
my ($self, $time) = @_; |
|
268
|
|
|
|
|
|
|
# _tick() guarantee $time > EMPTY |
|
269
|
0
|
|
|
|
|
0
|
my $len = @{ $self->{data} }; |
|
|
0
|
|
|
|
|
0
|
|
|
270
|
0
|
|
|
|
|
0
|
for (1 .. $len) { |
|
271
|
0
|
|
|
|
|
0
|
$_ = ($self->{next} + $_ - 1) % $len; |
|
272
|
0
|
0
|
|
|
|
0
|
return $self->{data}[ $_ ] if $self->{data}[ $_ ] > $time; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
0
|
|
|
|
|
0
|
return; |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
sub del { |
|
278
|
13
|
|
|
13
|
|
19
|
my ($self, $time, $quantity) = @_; |
|
279
|
|
|
|
|
|
|
# try_acquire() guarantee $quantity > 0 |
|
280
|
|
|
|
|
|
|
# even if $time is already outdated, these elements should be removed |
|
281
|
|
|
|
|
|
|
# anyway in case {period} will be increased later |
|
282
|
13
|
|
|
|
|
18
|
my $len = @{ $self->{data} }; |
|
|
13
|
|
|
|
|
21
|
|
|
283
|
13
|
50
|
|
|
|
48
|
if (!$len) { |
|
284
|
0
|
|
|
|
|
0
|
return; |
|
285
|
|
|
|
|
|
|
} |
|
286
|
13
|
50
|
|
|
|
33
|
if ($quantity > $len) { |
|
287
|
0
|
|
|
|
|
0
|
$quantity = $len; |
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
# OPTIMIZATION not in {data} |
|
290
|
13
|
50
|
|
|
|
75
|
if ($self->{data}[ $self->{next} ] > $time) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
291
|
0
|
|
|
|
|
0
|
return; |
|
292
|
|
|
|
|
|
|
} |
|
293
|
|
|
|
|
|
|
# OPTIMIZATION oldest |
|
294
|
|
|
|
|
|
|
elsif ($self->{data}[ $self->{next} ] == $time) { |
|
295
|
11
|
|
|
|
|
27
|
for (map { ($self->{next} + $_ - 1) % $len } 1 .. $quantity) { |
|
|
11
|
|
|
|
|
50
|
|
|
296
|
|
|
|
|
|
|
# part of $quantity may be not in {data} (if {limit} was decreased) |
|
297
|
11
|
50
|
|
|
|
37
|
return if $self->{data}[ $_ ] != $time; |
|
298
|
11
|
|
|
|
|
30
|
$self->{data}[ $_ ] = EMPTY; |
|
299
|
|
|
|
|
|
|
} |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
# OPTIMIZATION newest |
|
302
|
|
|
|
|
|
|
elsif ($self->{data}[ $self->{next} - 1 ] == $time) { |
|
303
|
2
|
|
|
|
|
6
|
for (map { $self->{next} - $_ } 1 .. $quantity) { |
|
|
2
|
|
|
|
|
6
|
|
|
304
|
2
|
50
|
|
|
|
17
|
croak 'assert: newest: no time' if $self->{data}[ $_ ] != $time; |
|
305
|
2
|
|
|
|
|
6
|
$self->{data}[ $_ ] = EMPTY; |
|
306
|
|
|
|
|
|
|
} |
|
307
|
2
|
|
|
|
|
8
|
$self->{next} = ($self->{next} - $quantity) % $len; |
|
308
|
|
|
|
|
|
|
} |
|
309
|
|
|
|
|
|
|
# middle (actually it support any case, not just middle) |
|
310
|
|
|
|
|
|
|
else { |
|
311
|
0
|
|
0
|
|
|
0
|
my $i = _binsearch($time, $self->{data}, $self->{next}, $len - 1) |
|
312
|
|
|
|
|
|
|
// _binsearch($time, $self->{data}, 0, $self->{next} - 1); |
|
313
|
0
|
0
|
|
|
|
0
|
croak 'assert: middle: not found' if !defined $i; |
|
314
|
0
|
|
|
|
|
0
|
for (map { ($i + $_ - 1) % $len } 1 .. $quantity) { |
|
|
0
|
|
|
|
|
0
|
|
|
315
|
0
|
0
|
|
|
|
0
|
croak 'assert: middle: no time' if $self->{data}[ $_ ] != $time; |
|
316
|
0
|
|
|
|
|
0
|
$self->{data}[ $_ ] = EMPTY; |
|
317
|
|
|
|
|
|
|
} |
|
318
|
|
|
|
|
|
|
# OPTIMIZATION move minimum amount of elements |
|
319
|
0
|
|
|
|
|
0
|
my $count_rew = ($self->{next} - $i) % $len; |
|
320
|
0
|
|
|
|
|
0
|
my $count_fwd = ($i + $quantity - $self->{next}) % $len; |
|
321
|
|
|
|
|
|
|
# move oldest elements forward |
|
322
|
0
|
0
|
|
|
|
0
|
if ($count_fwd <= $count_rew) { |
|
323
|
0
|
|
|
|
|
0
|
@{ $self->{data} }[ map { ($self->{next}+$_-1) % $len } 1 .. $count_fwd ] = |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
324
|
0
|
|
|
|
|
0
|
@{ $self->{data} }[ map { ($self->{next}+$_-1) % $len } $count_fwd-$quantity+1 .. $count_fwd, 1 .. $count_fwd-$quantity ]; |
|
|
0
|
|
|
|
|
0
|
|
|
325
|
|
|
|
|
|
|
} |
|
326
|
|
|
|
|
|
|
# move newest elements backward |
|
327
|
|
|
|
|
|
|
else { |
|
328
|
0
|
|
|
|
|
0
|
@{ $self->{data} }[ map { ($i+$_-1) % $len } 1 .. $count_rew ] = |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
329
|
0
|
|
|
|
|
0
|
@{ $self->{data} }[ map { ($i+$_-1) % $len } 1+$quantity .. $count_rew, 1 .. $quantity]; |
|
|
0
|
|
|
|
|
0
|
|
|
330
|
0
|
|
|
|
|
0
|
$self->{next} = ($self->{next} - $quantity) % $len; |
|
331
|
|
|
|
|
|
|
} |
|
332
|
|
|
|
|
|
|
} |
|
333
|
13
|
|
|
|
|
26
|
return; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub get { |
|
337
|
24
|
|
|
24
|
|
35
|
my ($self, $id) = @_; |
|
338
|
|
|
|
|
|
|
# $id is number of required element, counting from oldest one ($id = 1) |
|
339
|
24
|
|
|
|
|
28
|
my $len = @{ $self->{data} }; |
|
|
24
|
|
|
|
|
48
|
|
|
340
|
|
|
|
|
|
|
# acquire() guarantee 0 < $id <= $len |
|
341
|
24
|
|
|
|
|
34
|
my $i = ($self->{next} + $id - 1) % $len; |
|
342
|
24
|
|
|
|
|
81
|
return $self->{data}[$i]; |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
sub resize { |
|
346
|
0
|
|
|
0
|
|
|
my ($self, $newlen) = @_; |
|
347
|
|
|
|
|
|
|
# limit() guarantee $newlen >= 0 |
|
348
|
0
|
|
|
|
|
|
my $len = @{ $self->{data} }; |
|
|
0
|
|
|
|
|
|
|
|
349
|
0
|
|
|
|
|
|
my $d = $self->{data}; |
|
350
|
0
|
|
|
|
|
|
$self->{data} = [ @{$d}[ $self->{next} .. $#{$d} ], @{$d}[ 0 .. $self->{next} - 1 ] ]; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
351
|
0
|
0
|
|
|
|
|
if ($newlen < $len) { |
|
352
|
0
|
|
|
|
|
|
$self->{next} = 0; |
|
353
|
0
|
|
|
|
|
|
splice @{ $self->{data} }, 0, $len - $newlen; |
|
|
0
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
} else { |
|
355
|
0
|
|
|
|
|
|
$self->{next} = $len % $newlen; |
|
356
|
0
|
|
|
|
|
|
push @{ $self->{data} }, (EMPTY) x ($newlen - $len); |
|
|
0
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
} |
|
358
|
0
|
|
|
|
|
|
return $self; |
|
359
|
|
|
|
|
|
|
} |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# From List::BinarySearch::PP version 0.23. |
|
362
|
|
|
|
|
|
|
# Modified to support slices and work with array of numbers, without callback. |
|
363
|
|
|
|
|
|
|
sub _binsearch { |
|
364
|
0
|
|
|
0
|
|
|
my ( $target, $aref, $min, $max ) = @_; |
|
365
|
0
|
|
0
|
|
|
|
$min //= 0; |
|
366
|
0
|
|
0
|
|
|
|
$max //= $#{$aref}; |
|
|
0
|
|
|
|
|
|
|
|
367
|
0
|
0
|
0
|
|
|
|
croak 'bad slice' if $min < 0 || $#{$aref} < $max || $min > $max; |
|
|
0
|
|
0
|
|
|
|
|
|
368
|
0
|
|
|
|
|
|
while ( $max > $min ) { |
|
369
|
0
|
|
|
|
|
|
my $mid = int( ( $min + $max ) / 2 ); |
|
370
|
0
|
0
|
|
|
|
|
if ( $target > $aref->[$mid] ) { |
|
371
|
0
|
|
|
|
|
|
$min = $mid + 1; |
|
372
|
|
|
|
|
|
|
} else { |
|
373
|
0
|
|
|
|
|
|
$max = $mid; |
|
374
|
|
|
|
|
|
|
} |
|
375
|
|
|
|
|
|
|
} |
|
376
|
0
|
0
|
|
|
|
|
return $min if $target == $aref->[$min]; |
|
377
|
0
|
|
|
|
|
|
return; |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
1; # Magic true value required at end of module |
|
382
|
|
|
|
|
|
|
__END__ |