berater 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,118 +1,80 @@
1
1
  describe Berater::Matchers::Overloaded do
2
2
 
3
3
  context 'Berater::Unlimiter' do
4
- let(:limiter) { Berater.new(:key, :unlimited) }
4
+ let(:limiter) { Berater::Unlimiter.new }
5
5
 
6
6
  it { expect(limiter).not_to be_overloaded }
7
- it { expect(limiter).not_to be_inhibited }
8
- it { expect(limiter).not_to be_overrated }
9
- it { expect(limiter).not_to be_incapacitated }
10
-
11
7
  it { expect { limiter }.not_to be_overloaded }
12
- it { expect { limiter }.not_to be_inhibited }
13
- it { expect { limiter }.not_to be_overrated }
14
- it { expect { limiter }.not_to be_incapacitated }
15
-
16
8
  it { expect { limiter.limit }.not_to be_overloaded }
17
- it { expect { limiter.limit }.not_to be_inhibited }
18
- it { expect { limiter.limit }.not_to be_overrated }
19
- it { expect { limiter.limit }.not_to be_incapacitated }
20
9
  end
21
10
 
22
11
  context 'Berater::Inhibitor' do
23
- let(:limiter) { Berater.new(:key, :inhibited) }
12
+ let(:limiter) { Berater::Inhibitor.new }
24
13
 
25
14
  it { expect(limiter).to be_overloaded }
26
- it { expect(limiter).to be_inhibited }
27
-
28
15
  it { expect { limiter }.to be_overloaded }
29
- it { expect { limiter }.to be_inhibited }
30
-
31
16
  it { expect { limiter.limit }.to be_overloaded }
32
- it { expect { limiter.limit }.to be_inhibited }
33
17
  end
34
18
 
35
19
  context 'Berater::RateLimiter' do
36
- let(:limiter) { Berater.new(:key, 1, :second) }
20
+ let(:limiter) { Berater::RateLimiter.new(:key, 1, :second) }
37
21
 
38
22
  it { expect(limiter).not_to be_overloaded }
39
- it { expect(limiter).not_to be_inhibited }
40
- it { expect(limiter).not_to be_overrated }
41
- it { expect(limiter).not_to be_incapacitated }
42
-
43
23
  it { expect { limiter }.not_to be_overloaded }
44
- it { expect { limiter }.not_to be_inhibited }
45
- it { expect { limiter }.not_to be_overrated }
46
- it { expect { limiter }.not_to be_incapacitated }
47
-
48
24
  it { expect { limiter.limit }.not_to be_overloaded }
49
- it { expect { limiter.limit }.not_to be_inhibited }
50
- it { expect { limiter.limit }.not_to be_overrated }
51
- it { expect { limiter.limit }.not_to be_incapacitated }
52
25
 
53
26
  context 'once limit is used up' do
54
27
  before { limiter.limit }
55
28
 
56
- it 'should be_overrated' do
57
- expect(limiter).to be_overrated
29
+ it 'should be_overloaded' do
30
+ expect(limiter).to be_overloaded
58
31
  end
59
32
 
60
- it 'should be_overrated' do
61
- expect { limiter }.to be_overrated
33
+ it 'should be_overloaded' do
34
+ expect { limiter }.to be_overloaded
62
35
  end
63
36
 
64
- it 'should be_overrated' do
65
- expect { limiter.limit }.to be_overrated
37
+ it 'should be_overloaded' do
38
+ expect { limiter.limit }.to be_overloaded
66
39
  end
67
40
  end
68
41
  end
69
42
 
70
43
  context 'Berater::ConcurrencyLimiter' do
71
- let(:limiter) { Berater.new(:key, 1) }
44
+ let(:limiter) { Berater::ConcurrencyLimiter.new(:key, 1) }
72
45
 
73
46
  it { expect(limiter).not_to be_overloaded }
74
- it { expect(limiter).not_to be_inhibited }
75
- it { expect(limiter).not_to be_overrated }
76
- it { expect(limiter).not_to be_incapacitated }
77
-
78
47
  it { expect { limiter }.not_to be_overloaded }
79
- it { expect { limiter }.not_to be_inhibited }
80
- it { expect { limiter }.not_to be_overrated }
81
- it { expect { limiter }.not_to be_incapacitated }
82
-
83
48
  it { expect { limiter.limit }.not_to be_overloaded }
84
- it { expect { limiter.limit }.not_to be_inhibited }
85
- it { expect { limiter.limit }.not_to be_overrated }
86
- it { expect { limiter.limit }.not_to be_incapacitated }
87
49
 
88
50
  context 'when lock is released' do
89
- it 'should be_incapacitated' do
51
+ it 'should be_overloaded' do
90
52
  3.times do
91
- expect(limiter).not_to be_incapacitated
53
+ expect(limiter).not_to be_overloaded
92
54
  end
93
55
  end
94
56
 
95
- it 'should be_incapacitated' do
57
+ it 'should be_overloaded' do
96
58
  3.times do
97
- expect { limiter }.not_to be_incapacitated
59
+ expect { limiter }.not_to be_overloaded
98
60
  end
99
61
  end
100
62
 
101
- it 'should be_incapacitated' do
63
+ it 'should be_overloaded' do
102
64
  3.times do
103
- expect { limiter.limit {} }.not_to be_incapacitated
65
+ expect { limiter.limit {} }.not_to be_overloaded
104
66
  end
105
67
  end
106
68
  end
107
69
 
108
70
  context 'when lock is *not* released' do
109
- it 'should be_incapacitated' do
110
- expect { limiter.limit }.not_to be_incapacitated
111
- expect { limiter.limit }.to be_incapacitated
71
+ it 'should be_overloaded' do
72
+ expect { limiter.limit }.not_to be_overloaded
73
+ expect { limiter.limit }.to be_overloaded
112
74
  end
113
75
 
114
- it 'should be_incapacitated' do
115
- expect { 3.times { limiter.limit } }.to be_incapacitated
76
+ it 'should be_overloaded' do
77
+ expect { 3.times { limiter.limit } }.to be_overloaded
116
78
  end
117
79
  end
118
80
  end
@@ -156,31 +118,5 @@ describe Berater::Matchers::Overloaded do
156
118
  expect { raise Berater::Overloaded }.not_to be_overloaded
157
119
  }.to fail_including("did not expect #{Berater::Overloaded} to be raised")
158
120
  end
159
-
160
- it 'supports different verbs' do
161
- expect {
162
- expect { unlimiter }.to be_overrated
163
- }.to fail_including('expected to be overrated')
164
-
165
- expect {
166
- expect { unlimiter }.to be_incapacitated
167
- }.to fail_including('expected to be incapacitated')
168
- end
169
-
170
- it 'supports different exceptions' do
171
- expect {
172
- expect { 123 }.to be_overrated
173
- }.to fail_including(
174
- "expected #{Berater::RateLimiter::Overrated} to be raised"
175
- )
176
-
177
- expect {
178
- expect {
179
- raise Berater::ConcurrencyLimiter::Incapacitated
180
- }.not_to be_incapacitated
181
- }.to fail_including(
182
- "did not expect #{Berater::ConcurrencyLimiter::Incapacitated} to be raised"
183
- )
184
- end
185
121
  end
186
122
  end
@@ -1,5 +1,6 @@
1
1
  describe Berater::RateLimiter do
2
- it_behaves_like 'a limiter', Berater.new(:key, 3, :second)
2
+ it_behaves_like 'a limiter', described_class.new(:key, 3, :second)
3
+ it_behaves_like 'a limiter', described_class.new(:key, 3.5, :second)
3
4
 
4
5
  describe '.new' do
5
6
  let(:limiter) { described_class.new(:key, 1, :second) }
@@ -36,6 +37,7 @@ describe Berater::RateLimiter do
36
37
  it { expect_bad_capacity(-1) }
37
38
  it { expect_bad_capacity('1') }
38
39
  it { expect_bad_capacity(:one) }
40
+ it { expect_bad_capacity(Float::INFINITY) }
39
41
  end
40
42
  end
41
43
 
@@ -46,7 +48,7 @@ describe Berater::RateLimiter do
46
48
 
47
49
  it 'saves the interval in original and millisecond format' do
48
50
  expect(subject.interval).to be :second
49
- expect(subject.instance_variable_get(:@interval_msec)).to be 10**3
51
+ expect(subject.instance_variable_get(:@interval)).to be 10**3
50
52
  end
51
53
 
52
54
  it 'must be > 0' do
@@ -75,42 +77,42 @@ describe Berater::RateLimiter do
75
77
  it 'limits excessive calls' do
76
78
  3.times { limiter.limit }
77
79
 
78
- expect(limiter).to be_overrated
80
+ expect(limiter).to be_overloaded
79
81
  end
80
82
 
81
83
  it 'resets limit over time' do
82
84
  3.times { limiter.limit }
83
- expect(limiter).to be_overrated
85
+ expect(limiter).to be_overloaded
84
86
 
85
87
  Timecop.freeze(1)
86
88
 
87
89
  3.times { limiter.limit }
88
- expect(limiter).to be_overrated
90
+ expect(limiter).to be_overloaded
89
91
  end
90
92
 
91
93
  context 'with millisecond precision' do
92
94
  it 'resets limit over time' do
93
95
  3.times { limiter.limit }
94
- expect(limiter).to be_overrated
96
+ expect(limiter).to be_overloaded
95
97
 
96
98
  # travel forward to just before the count decrements
97
99
  Timecop.freeze(0.333)
98
- expect(limiter).to be_overrated
100
+ expect(limiter).to be_overloaded
99
101
 
100
102
  # traveling one more millisecond will decrement the count
101
103
  Timecop.freeze(0.001)
102
104
  limiter.limit
103
- expect(limiter).to be_overrated
105
+ expect(limiter).to be_overloaded
104
106
  end
105
107
 
106
108
  it 'works when drip rate is < 1 per millisecond' do
107
109
  limiter = described_class.new(:key, 2_000, :second)
108
110
 
109
111
  limiter.capacity.times { limiter.limit }
110
- expect(limiter).to be_overrated
112
+ expect(limiter).to be_overloaded
111
113
 
112
114
  Timecop.freeze(0.001)
113
- expect(limiter).not_to be_overrated
115
+ expect(limiter).not_to be_overloaded
114
116
 
115
117
  2.times { limiter.limit }
116
118
  end
@@ -121,9 +123,9 @@ describe Berater::RateLimiter do
121
123
 
122
124
  it 'still works' do
123
125
  limiter.limit
124
- expect(limiter).not_to be_overrated
126
+ expect(limiter).not_to be_overloaded
125
127
 
126
- expect { limiter.limit }.to be_overrated
128
+ expect { limiter.limit }.to be_overloaded
127
129
 
128
130
  limiter.limit(cost: 0.5)
129
131
  end
@@ -132,30 +134,72 @@ describe Berater::RateLimiter do
132
134
  it 'accepts a dynamic capacity' do
133
135
  limiter = described_class.new(:key, 1, :second)
134
136
 
135
- expect { limiter.limit(capacity: 0) }.to be_overrated
137
+ expect { limiter.limit(capacity: 0) }.to be_overloaded
136
138
  5.times { limiter.limit(capacity: 10) }
137
- expect { limiter }.to be_overrated
139
+ expect { limiter }.to be_overloaded
138
140
  end
139
141
 
140
142
  context 'works with cost parameter' do
141
- it { expect { limiter.limit(cost: 4) }.to be_overrated }
143
+ it { expect { limiter.limit(cost: 4) }.to be_overloaded }
142
144
 
143
145
  it 'works within limit' do
144
146
  limiter.limit(cost: 3)
145
- expect { limiter.limit }.to be_overrated
147
+ expect { limiter.limit }.to be_overloaded
146
148
  end
147
149
 
148
150
  it 'resets over time' do
149
151
  limiter.limit(cost: 3)
150
- expect(limiter).to be_overrated
152
+ expect(limiter).to be_overloaded
151
153
 
152
154
  Timecop.freeze(1)
153
- expect(limiter).not_to be_overrated
155
+ expect(limiter).not_to be_overloaded
154
156
  end
155
157
 
156
- it 'can be a Float' do
157
- 2.times { limiter.limit(cost: 1.5) }
158
- expect(limiter).to be_overrated
158
+ context 'when cost is a Float' do
159
+ it 'still works' do
160
+ 2.times { limiter.limit(cost: 1.5) }
161
+ expect(limiter).to be_overloaded
162
+ end
163
+
164
+ it 'calculates contention correctly' do
165
+ # note: Redis must return Floats as strings to maintain precision
166
+ lock = limiter.limit(cost: 1.5)
167
+ expect(lock.contention).to be 1.5
168
+ end
169
+ end
170
+ end
171
+
172
+ context 'with clock skew' do
173
+ let(:limiter) { described_class.new(:key, 10, :second) }
174
+
175
+ it 'works skewing backward' do
176
+ limiter.limit(cost: 9)
177
+
178
+ Timecop.freeze(-0.1) do
179
+ limiter.limit
180
+ expect(limiter).to be_overloaded
181
+ end
182
+
183
+ expect(limiter).to be_overloaded
184
+
185
+ Timecop.freeze(0.1)
186
+ limiter.limit
187
+ expect(limiter).to be_overloaded
188
+ end
189
+
190
+ it 'works skewing forward' do
191
+ limiter.limit
192
+
193
+ Timecop.freeze(0.1) do
194
+ # one drip later
195
+ limiter.limit(cost: 10)
196
+ expect(limiter).to be_overloaded
197
+ end
198
+
199
+ expect(limiter).to be_overloaded
200
+
201
+ Timecop.freeze(0.1)
202
+ expect(limiter).to be_overloaded
159
203
  end
160
204
  end
161
205
 
@@ -164,10 +208,10 @@ describe Berater::RateLimiter do
164
208
  let(:limiter_two) { described_class.new(:key, 1, :second) }
165
209
 
166
210
  it 'works as expected' do
167
- expect(limiter_one.limit).not_to be_overrated
211
+ expect(limiter_one.limit).not_to be_overloaded
168
212
 
169
- expect(limiter_one).to be_overrated
170
- expect(limiter_two).to be_overrated
213
+ expect(limiter_one).to be_overloaded
214
+ expect(limiter_two).to be_overloaded
171
215
  end
172
216
  end
173
217
 
@@ -176,27 +220,33 @@ describe Berater::RateLimiter do
176
220
  let(:limiter_two) { described_class.new(:two, 2, :second) }
177
221
 
178
222
  it 'works as expected' do
179
- expect(limiter_one.limit).not_to be_overrated
180
- expect(limiter_two.limit).not_to be_overrated
223
+ expect(limiter_one.limit).not_to be_overloaded
224
+ expect(limiter_two.limit).not_to be_overloaded
181
225
 
182
- expect(limiter_one).to be_overrated
183
- expect(limiter_two.limit).not_to be_overrated
226
+ expect(limiter_one).to be_overloaded
227
+ expect(limiter_two.limit).not_to be_overloaded
184
228
 
185
- expect(limiter_one).to be_overrated
186
- expect(limiter_two).to be_overrated
229
+ expect(limiter_one).to be_overloaded
230
+ expect(limiter_two).to be_overloaded
187
231
  end
188
232
  end
189
233
  end
190
234
 
191
- describe '#overloaded?' do
192
- let(:limiter) { described_class.new(:key, 1, :second) }
235
+ describe '#utilization' do
236
+ let(:limiter) { described_class.new(:key, 10, :minute) }
193
237
 
194
- it 'works' do
195
- expect(limiter.overloaded?).to be false
196
- limiter.limit
197
- expect(limiter.overloaded?).to be true
198
- Timecop.freeze(1)
199
- expect(limiter.overloaded?).to be false
238
+ it do
239
+ expect(limiter.utilization).to be 0.0
240
+
241
+ 2.times { limiter.limit }
242
+ expect(limiter.utilization).to be 0.2
243
+
244
+ 8.times { limiter.limit }
245
+ expect(limiter.utilization).to be 1.0
246
+
247
+ Timecop.freeze(30)
248
+
249
+ expect(limiter.utilization).to be 0.5
200
250
  end
201
251
  end
202
252
 
data/spec/riddle_spec.rb CHANGED
@@ -42,11 +42,15 @@ class ConcurrenyRiddler
42
42
  timeout ||= 1_000 # fake infinity
43
43
 
44
44
  limiter = Berater::RateLimiter.new(:key, capacity, timeout)
45
- limiter.limit
45
+ lock = limiter.limit
46
46
  yield if block_given?
47
47
  ensure
48
48
  # decrement counter
49
- limiter.redis.decr(limiter.send(:cache_key, :key))
49
+ if lock
50
+ key = limiter.send(:cache_key)
51
+ count, ts = limiter.redis.get(key).split ';'
52
+ limiter.redis.set(key, "#{count.to_f - 1};#{ts}")
53
+ end
50
54
  end
51
55
  end
52
56
 
@@ -0,0 +1,79 @@
1
+ describe Berater::StaticLimiter do
2
+ it_behaves_like 'a limiter', described_class.new(:key, 3)
3
+ it_behaves_like 'a limiter', described_class.new(:key, 3.5)
4
+
5
+ describe '#limit' do
6
+ let(:limiter) { described_class.new(:key, 3) }
7
+
8
+ it 'limits excessive calls' do
9
+ 3.times { limiter.limit }
10
+
11
+ expect(limiter).to be_overloaded
12
+ end
13
+
14
+ context 'when capacity is a Float' do
15
+ let(:limiter) { described_class.new(:key, 1.5) }
16
+
17
+ it 'still works' do
18
+ limiter.limit
19
+ expect(limiter).not_to be_overloaded
20
+
21
+ expect { limiter.limit }.to be_overloaded
22
+
23
+ limiter.limit(cost: 0.5)
24
+ end
25
+ end
26
+
27
+ it 'accepts a dynamic capacity' do
28
+ limiter = described_class.new(:key, 1)
29
+
30
+ expect { limiter.limit(capacity: 0) }.to be_overloaded
31
+ 5.times { limiter.limit(capacity: 10) }
32
+ expect { limiter }.to be_overloaded
33
+ end
34
+
35
+ context 'works with cost parameter' do
36
+ let(:limiter) { described_class.new(:key, 3) }
37
+
38
+ it { expect { limiter.limit(cost: 4) }.to be_overloaded }
39
+
40
+ it 'works within limit' do
41
+ limiter.limit(cost: 3)
42
+ expect { limiter.limit }.to be_overloaded
43
+ end
44
+
45
+ context 'when cost is a Float' do
46
+ it 'still works' do
47
+ 2.times { limiter.limit(cost: 1.5) }
48
+ expect(limiter).to be_overloaded
49
+ end
50
+
51
+ it 'calculates contention correctly' do
52
+ lock = limiter.limit(cost: 1.5)
53
+ expect(lock.contention).to be 1.5
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#utilization' do
60
+ let(:limiter) { described_class.new(:key, 10) }
61
+
62
+ it do
63
+ expect(limiter.utilization).to be 0.0
64
+
65
+ 2.times { limiter.limit }
66
+ expect(limiter.utilization).to be 0.2
67
+
68
+ 8.times { limiter.limit }
69
+ expect(limiter.utilization).to be 1.0
70
+ end
71
+ end
72
+
73
+ describe '#to_s' do
74
+ let(:limiter) { described_class.new(:key, 3) }
75
+
76
+ it { expect(limiter.to_s).to include '3' }
77
+ end
78
+
79
+ end