berater 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/berater.rb +17 -24
- data/lib/berater/base_limiter.rb +11 -19
- data/lib/berater/concurrency_limiter.rb +28 -88
- data/lib/berater/inhibitor.rb +3 -9
- data/lib/berater/lock.rb +36 -0
- data/lib/berater/rate_limiter.rb +14 -16
- data/lib/berater/unlimiter.rb +14 -9
- data/lib/berater/version.rb +1 -1
- data/spec/berater_spec.rb +31 -98
- data/spec/concurrency_limiter_spec.rb +58 -73
- data/spec/concurrency_lock_spec.rb +26 -21
- data/spec/inhibitor_spec.rb +3 -15
- data/spec/matcher_spec.rb +4 -4
- data/spec/rate_limiter_spec.rb +27 -50
- data/spec/rate_lock_spec.rb +20 -0
- data/spec/unlimiter_spec.rb +7 -31
- metadata +5 -2
data/lib/berater/version.rb
CHANGED
data/spec/berater_spec.rb
CHANGED
@@ -9,10 +9,10 @@ describe Berater do
|
|
9
9
|
describe '.configure' do
|
10
10
|
it 'can be set via configure' do
|
11
11
|
Berater.configure do |c|
|
12
|
-
c.
|
12
|
+
c.redis = :redis
|
13
13
|
end
|
14
14
|
|
15
|
-
expect(Berater.
|
15
|
+
expect(Berater.redis).to eq :redis
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -24,20 +24,13 @@ describe Berater do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
describe '.
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context 'unlimited mode' do
|
34
|
-
before { Berater.mode = :unlimited }
|
35
|
-
|
36
|
-
describe '.new' do
|
37
|
-
let(:limiter) { Berater.new(:unlimited) }
|
27
|
+
describe '.new' do
|
28
|
+
context 'unlimited mode' do
|
29
|
+
let(:limiter) { Berater.new(:key, :unlimited) }
|
38
30
|
|
39
31
|
it 'instantiates an Unlimiter' do
|
40
32
|
expect(limiter).to be_a Berater::Unlimiter
|
33
|
+
expect(limiter.key).to be :key
|
41
34
|
end
|
42
35
|
|
43
36
|
it 'inherits redis' do
|
@@ -46,35 +39,22 @@ describe Berater do
|
|
46
39
|
|
47
40
|
it 'accepts options' do
|
48
41
|
redis = double('Redis')
|
49
|
-
limiter = Berater.new(:
|
50
|
-
expect(limiter.key).to match(/key/)
|
42
|
+
limiter = Berater.new(:key, :unlimited, redis: redis)
|
51
43
|
expect(limiter.redis).to be redis
|
52
44
|
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '.limit' do
|
56
|
-
it 'works' do
|
57
|
-
expect(Berater.limit).to be_nil
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'yields' do
|
61
|
-
expect {|b| Berater.limit(&b) }.to yield_control
|
62
|
-
end
|
63
45
|
|
64
|
-
it '
|
65
|
-
|
46
|
+
it 'works with convinience' do
|
47
|
+
expect(Berater).to receive(:new).and_return(limiter)
|
48
|
+
expect {|b| Berater(:key, :unlimited, &b) }.to yield_control
|
66
49
|
end
|
67
50
|
end
|
68
|
-
end
|
69
|
-
|
70
|
-
context 'inhibited mode' do
|
71
|
-
before { Berater.mode = :inhibited }
|
72
51
|
|
73
|
-
|
74
|
-
let(:limiter) { Berater.new(:inhibited) }
|
52
|
+
context 'inhibited mode' do
|
53
|
+
let(:limiter) { Berater.new(:key, :inhibited) }
|
75
54
|
|
76
55
|
it 'instantiates an Inhibitor' do
|
77
56
|
expect(limiter).to be_a Berater::Inhibitor
|
57
|
+
expect(limiter.key).to be :key
|
78
58
|
end
|
79
59
|
|
80
60
|
it 'inherits redis' do
|
@@ -83,27 +63,22 @@ describe Berater do
|
|
83
63
|
|
84
64
|
it 'accepts options' do
|
85
65
|
redis = double('Redis')
|
86
|
-
limiter = Berater.new(:
|
87
|
-
expect(limiter.key).to match(/key/)
|
66
|
+
limiter = Berater.new(:key, :inhibited, redis: redis)
|
88
67
|
expect(limiter.redis).to be redis
|
89
68
|
end
|
90
|
-
end
|
91
69
|
|
92
|
-
|
93
|
-
|
94
|
-
expect { Berater
|
70
|
+
it 'works with convinience' do
|
71
|
+
expect(Berater).to receive(:new).and_return(limiter)
|
72
|
+
expect { Berater(:key, :inhibited) }.to be_inhibited
|
95
73
|
end
|
96
74
|
end
|
97
|
-
end
|
98
|
-
|
99
|
-
context 'rate mode' do
|
100
|
-
before { Berater.mode = :rate }
|
101
75
|
|
102
|
-
|
103
|
-
let(:limiter) { Berater.new(:rate, 1, :second) }
|
76
|
+
context 'rate mode' do
|
77
|
+
let(:limiter) { Berater.new(:key, :rate, 1, :second) }
|
104
78
|
|
105
79
|
it 'instantiates a RateLimiter' do
|
106
80
|
expect(limiter).to be_a Berater::RateLimiter
|
81
|
+
expect(limiter.key).to be :key
|
107
82
|
end
|
108
83
|
|
109
84
|
it 'inherits redis' do
|
@@ -112,44 +87,22 @@ describe Berater do
|
|
112
87
|
|
113
88
|
it 'accepts options' do
|
114
89
|
redis = double('Redis')
|
115
|
-
limiter = Berater.new(:rate, 1, :second,
|
116
|
-
expect(limiter.key).to match(/key/)
|
90
|
+
limiter = Berater.new(:key, :rate, 1, :second, redis: redis)
|
117
91
|
expect(limiter.redis).to be redis
|
118
92
|
end
|
119
|
-
end
|
120
|
-
|
121
|
-
describe '.limit' do
|
122
|
-
it 'works' do
|
123
|
-
expect(Berater.limit(1, :second)).to eq 1
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'yields' do
|
127
|
-
expect {|b| Berater.limit(2, :second, &b) }.to yield_control
|
128
|
-
expect(Berater.limit(2, :second) { 123 }).to eq 123
|
129
|
-
end
|
130
93
|
|
131
|
-
it '
|
132
|
-
expect(Berater.
|
133
|
-
expect { Berater
|
134
|
-
end
|
135
|
-
|
136
|
-
it 'accepts options' do
|
137
|
-
redis = double('Redis')
|
138
|
-
expect(redis).to receive(:multi)
|
139
|
-
|
140
|
-
Berater.limit(1, :second, redis: redis) rescue nil
|
94
|
+
it 'works with convinience' do
|
95
|
+
expect(Berater).to receive(:new).and_return(limiter)
|
96
|
+
expect {|b| Berater(:key, :rate, 1, :second, &b) }.to yield_control
|
141
97
|
end
|
142
98
|
end
|
143
|
-
end
|
144
99
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
describe '.limiter' do
|
149
|
-
let(:limiter) { Berater.new(:concurrency, 1) }
|
100
|
+
context 'concurrency mode' do
|
101
|
+
let(:limiter) { Berater.new(:key, :concurrency, 1) }
|
150
102
|
|
151
103
|
it 'instantiates a ConcurrencyLimiter' do
|
152
104
|
expect(limiter).to be_a Berater::ConcurrencyLimiter
|
105
|
+
expect(limiter.key).to be :key
|
153
106
|
end
|
154
107
|
|
155
108
|
it 'inherits redis' do
|
@@ -158,33 +111,13 @@ describe Berater do
|
|
158
111
|
|
159
112
|
it 'accepts options' do
|
160
113
|
redis = double('Redis')
|
161
|
-
limiter = Berater.new(:concurrency, 1,
|
162
|
-
expect(limiter.key).to match(/key/)
|
114
|
+
limiter = Berater.new(:key, :concurrency, 1, redis: redis)
|
163
115
|
expect(limiter.redis).to be redis
|
164
116
|
end
|
165
|
-
end
|
166
|
-
|
167
|
-
describe '.limit' do
|
168
|
-
it 'works (without blocks by returning a lock)' do
|
169
|
-
lock = Berater.limit(1)
|
170
|
-
expect(lock).to be_a Berater::ConcurrencyLimiter::Lock
|
171
|
-
expect(lock.release).to be true
|
172
|
-
end
|
173
|
-
|
174
|
-
it 'yields' do
|
175
|
-
expect {|b| Berater.limit(1, &b) }.to yield_control
|
176
|
-
end
|
177
|
-
|
178
|
-
it 'limits excessive calls' do
|
179
|
-
Berater.limit(1)
|
180
|
-
expect { Berater.limit(1) }.to be_incapacitated
|
181
|
-
end
|
182
|
-
|
183
|
-
it 'accepts options' do
|
184
|
-
redis = double('Redis')
|
185
|
-
expect(redis).to receive(:eval)
|
186
117
|
|
187
|
-
|
118
|
+
it 'works with convinience' do
|
119
|
+
expect(Berater).to receive(:new).and_return(limiter)
|
120
|
+
expect {|b| Berater(:key, :concurrency, 1, &b) }.to yield_control
|
188
121
|
end
|
189
122
|
end
|
190
123
|
end
|
@@ -1,22 +1,20 @@
|
|
1
1
|
describe Berater::ConcurrencyLimiter do
|
2
|
-
before { Berater.mode = :concurrency }
|
3
|
-
|
4
2
|
describe '.new' do
|
5
|
-
let(:limiter) { described_class.new(1) }
|
3
|
+
let(:limiter) { described_class.new(:key, 1) }
|
6
4
|
|
7
5
|
it 'initializes' do
|
6
|
+
expect(limiter.key).to be :key
|
8
7
|
expect(limiter.capacity).to be 1
|
9
8
|
end
|
10
9
|
|
11
10
|
it 'has default values' do
|
12
|
-
expect(limiter.key).to eq described_class.to_s
|
13
11
|
expect(limiter.redis).to be Berater.redis
|
14
12
|
end
|
15
13
|
end
|
16
14
|
|
17
15
|
describe '#capacity' do
|
18
16
|
def expect_capacity(capacity)
|
19
|
-
limiter = described_class.new(capacity)
|
17
|
+
limiter = described_class.new(:key, capacity)
|
20
18
|
expect(limiter.capacity).to eq capacity
|
21
19
|
end
|
22
20
|
|
@@ -27,7 +25,7 @@ describe Berater::ConcurrencyLimiter do
|
|
27
25
|
context 'with erroneous values' do
|
28
26
|
def expect_bad_capacity(capacity)
|
29
27
|
expect do
|
30
|
-
described_class.new(capacity)
|
28
|
+
described_class.new(:key, capacity)
|
31
29
|
end.to raise_error ArgumentError
|
32
30
|
end
|
33
31
|
|
@@ -40,7 +38,7 @@ describe Berater::ConcurrencyLimiter do
|
|
40
38
|
|
41
39
|
describe '#timeout' do
|
42
40
|
def expect_timeout(timeout)
|
43
|
-
limiter = described_class.new(1, timeout: timeout)
|
41
|
+
limiter = described_class.new(:key, 1, timeout: timeout)
|
44
42
|
expect(limiter.timeout).to eq timeout
|
45
43
|
end
|
46
44
|
|
@@ -51,7 +49,7 @@ describe Berater::ConcurrencyLimiter do
|
|
51
49
|
context 'with erroneous values' do
|
52
50
|
def expect_bad_timeout(timeout)
|
53
51
|
expect do
|
54
|
-
described_class.new(1, timeout: timeout)
|
52
|
+
described_class.new(:key, 1, timeout: timeout)
|
55
53
|
end.to raise_error ArgumentError
|
56
54
|
end
|
57
55
|
|
@@ -63,126 +61,113 @@ describe Berater::ConcurrencyLimiter do
|
|
63
61
|
end
|
64
62
|
|
65
63
|
describe '#limit' do
|
66
|
-
let(:limiter) { described_class.new(2, timeout: 1) }
|
64
|
+
let(:limiter) { described_class.new(:key, 2, timeout: 1) }
|
67
65
|
|
68
66
|
it 'works' do
|
69
67
|
expect {|b| limiter.limit(&b) }.to yield_control
|
70
68
|
end
|
71
69
|
|
72
|
-
it 'works many times if workers
|
70
|
+
it 'works many times if workers release locks' do
|
73
71
|
30.times do
|
74
72
|
expect {|b| limiter.limit(&b) }.to yield_control
|
75
73
|
end
|
74
|
+
|
75
|
+
30.times do
|
76
|
+
lock = limiter.limit
|
77
|
+
lock.release
|
78
|
+
end
|
76
79
|
end
|
77
80
|
|
78
81
|
it 'limits excessive calls' do
|
79
|
-
expect(limiter.limit).to be_a Berater::
|
80
|
-
expect(limiter.limit).to be_a Berater::
|
82
|
+
expect(limiter.limit).to be_a Berater::Lock
|
83
|
+
expect(limiter.limit).to be_a Berater::Lock
|
81
84
|
|
82
|
-
expect
|
85
|
+
expect(limiter).to be_incapacitated
|
83
86
|
end
|
84
87
|
|
85
88
|
it 'times out locks' do
|
86
|
-
expect(limiter.limit).to be_a Berater::
|
87
|
-
expect(limiter.limit).to be_a Berater::
|
88
|
-
expect
|
89
|
+
expect(limiter.limit).to be_a Berater::Lock
|
90
|
+
expect(limiter.limit).to be_a Berater::Lock
|
91
|
+
expect(limiter).to be_incapacitated
|
89
92
|
|
90
|
-
|
93
|
+
Timecop.travel(1)
|
91
94
|
|
92
|
-
expect(limiter.limit).to be_a Berater::
|
93
|
-
expect(limiter.limit).to be_a Berater::
|
94
|
-
expect
|
95
|
+
expect(limiter.limit).to be_a Berater::Lock
|
96
|
+
expect(limiter.limit).to be_a Berater::Lock
|
97
|
+
expect(limiter).to be_incapacitated
|
95
98
|
end
|
96
|
-
end
|
97
99
|
|
98
|
-
|
99
|
-
|
100
|
-
let(:limiter_two) { described_class.new(1) }
|
101
|
-
|
102
|
-
it { expect(limiter_one.key).to eq limiter_two.key }
|
103
|
-
|
104
|
-
it 'works as expected' do
|
105
|
-
expect(limiter_one.limit).to be_a Berater::ConcurrencyLimiter::Lock
|
100
|
+
context 'with capacity 0' do
|
101
|
+
let(:limiter) { described_class.new(:key, 0) }
|
106
102
|
|
107
|
-
|
108
|
-
|
103
|
+
it 'always fails' do
|
104
|
+
expect(limiter).to be_incapacitated
|
105
|
+
end
|
109
106
|
end
|
110
107
|
end
|
111
108
|
|
112
|
-
context 'with
|
113
|
-
let(:
|
114
|
-
|
115
|
-
it 'works as expected' do
|
116
|
-
one_lock = limiter.limit(key: :one)
|
117
|
-
expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
|
118
|
-
|
119
|
-
expect { limiter.limit(key: :one) {} }.to be_incapacitated
|
120
|
-
expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
|
121
|
-
|
122
|
-
two_lock = limiter.limit(key: :two)
|
123
|
-
expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
|
109
|
+
context 'with same key, different limiters' do
|
110
|
+
let(:limiter_one) { described_class.new(:key, 1) }
|
111
|
+
let(:limiter_two) { described_class.new(:key, 1) }
|
124
112
|
|
125
|
-
|
126
|
-
expect { limiter.limit(key: :two) {} }.to be_incapacitated
|
113
|
+
it { expect(limiter_one.key).to eq limiter_two.key }
|
127
114
|
|
128
|
-
|
129
|
-
expect
|
130
|
-
expect { limiter.limit(key: :two) {} }.to be_incapacitated
|
115
|
+
it 'works as expected' do
|
116
|
+
expect(limiter_one.limit).to be_a Berater::Lock
|
131
117
|
|
132
|
-
|
133
|
-
expect
|
134
|
-
expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
|
118
|
+
expect(limiter_one).to be_incapacitated
|
119
|
+
expect(limiter_two).to be_incapacitated
|
135
120
|
end
|
136
121
|
end
|
137
122
|
|
138
123
|
context 'with same key, different capacities' do
|
139
|
-
let(:limiter_one) { described_class.new(1) }
|
140
|
-
let(:limiter_two) { described_class.new(2) }
|
124
|
+
let(:limiter_one) { described_class.new(:key, 1) }
|
125
|
+
let(:limiter_two) { described_class.new(:key, 2) }
|
141
126
|
|
142
127
|
it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
|
143
128
|
|
144
129
|
it 'works as expected' do
|
145
130
|
one_lock = limiter_one.limit
|
146
|
-
expect(one_lock).to be_a Berater::
|
131
|
+
expect(one_lock).to be_a Berater::Lock
|
147
132
|
|
148
|
-
expect
|
149
|
-
expect
|
133
|
+
expect(limiter_one).to be_incapacitated
|
134
|
+
expect(limiter_two).not_to be_incapacitated
|
150
135
|
|
151
136
|
two_lock = limiter_two.limit
|
152
|
-
expect(two_lock).to be_a Berater::
|
137
|
+
expect(two_lock).to be_a Berater::Lock
|
153
138
|
|
154
|
-
expect
|
155
|
-
expect
|
139
|
+
expect(limiter_one).to be_incapacitated
|
140
|
+
expect(limiter_two).to be_incapacitated
|
156
141
|
|
157
142
|
one_lock.release
|
158
|
-
expect
|
159
|
-
expect
|
143
|
+
expect(limiter_one).to be_incapacitated
|
144
|
+
expect(limiter_two).not_to be_incapacitated
|
160
145
|
|
161
146
|
two_lock.release
|
162
|
-
expect
|
163
|
-
expect
|
147
|
+
expect(limiter_one).not_to be_incapacitated
|
148
|
+
expect(limiter_two).not_to be_incapacitated
|
164
149
|
end
|
165
150
|
end
|
166
151
|
|
167
152
|
context 'with different keys, different limiters' do
|
168
|
-
let(:limiter_one) { described_class.new(
|
169
|
-
let(:limiter_two) { described_class.new(
|
153
|
+
let(:limiter_one) { described_class.new(:one, 1) }
|
154
|
+
let(:limiter_two) { described_class.new(:two, 1) }
|
170
155
|
|
171
156
|
it 'works as expected' do
|
172
|
-
expect
|
173
|
-
expect
|
157
|
+
expect(limiter_one).not_to be_incapacitated
|
158
|
+
expect(limiter_two).not_to be_incapacitated
|
174
159
|
|
175
160
|
one_lock = limiter_one.limit
|
176
|
-
expect(one_lock).to be_a Berater::
|
161
|
+
expect(one_lock).to be_a Berater::Lock
|
177
162
|
|
178
|
-
expect
|
179
|
-
expect
|
163
|
+
expect(limiter_one).to be_incapacitated
|
164
|
+
expect(limiter_two).not_to be_incapacitated
|
180
165
|
|
181
166
|
two_lock = limiter_two.limit
|
182
|
-
expect(two_lock).to be_a Berater::
|
167
|
+
expect(two_lock).to be_a Berater::Lock
|
183
168
|
|
184
|
-
expect
|
185
|
-
expect
|
169
|
+
expect(limiter_one).to be_incapacitated
|
170
|
+
expect(limiter_two).to be_incapacitated
|
186
171
|
end
|
187
172
|
end
|
188
173
|
|
@@ -1,34 +1,39 @@
|
|
1
|
-
describe Berater::
|
2
|
-
|
1
|
+
describe Berater::Lock do
|
2
|
+
it_behaves_like 'a lock', Berater.new(:key, :concurrency, 3)
|
3
3
|
|
4
|
-
|
4
|
+
let(:limiter) { Berater.new(:key, :concurrency, 3) }
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
describe '#expired?' do
|
7
|
+
let!(:lock) { limiter.limit }
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
context 'when timeout is not set' do
|
10
|
+
it { expect(limiter.timeout).to eq 0 }
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
it 'never expires' do
|
13
|
+
expect(lock.locked?).to be true
|
14
|
+
expect(lock.expired?).to be false
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
Timecop.travel(1_000)
|
17
|
+
|
18
|
+
expect(lock.locked?).to be true
|
19
|
+
expect(lock.expired?).to be false
|
20
|
+
end
|
17
21
|
end
|
18
|
-
end
|
19
22
|
|
20
|
-
|
21
|
-
|
23
|
+
context 'when timeout is set and exceeded' do
|
24
|
+
before { Timecop.travel(1) }
|
22
25
|
|
23
|
-
|
24
|
-
expect(subject.expired?).to be true
|
25
|
-
end
|
26
|
+
let(:limiter) { Berater.new(:key, :concurrency, 3, timeout: 1) }
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
it 'expires' do
|
29
|
+
expect(lock.expired?).to be true
|
30
|
+
expect(lock.locked?).to be false
|
31
|
+
end
|
30
32
|
|
31
|
-
|
33
|
+
it 'fails to release' do
|
34
|
+
expect { lock.release }.to raise_error(RuntimeError, /expired/)
|
35
|
+
end
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
39
|
end
|