berater 0.6.1 → 0.9.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 +29 -15
- data/lib/berater/concurrency_limiter.rb +16 -23
- data/lib/berater/dsl.rb +8 -8
- data/lib/berater/inhibitor.rb +4 -4
- data/lib/berater/limiter.rb +55 -23
- data/lib/berater/limiter_set.rb +66 -0
- data/lib/berater/lock.rb +2 -1
- data/lib/berater/rate_limiter.rb +39 -31
- data/lib/berater/rspec.rb +1 -2
- data/lib/berater/rspec/matchers.rb +11 -31
- data/lib/berater/static_limiter.rb +49 -0
- data/lib/berater/test_mode.rb +27 -23
- data/lib/berater/unlimiter.rb +4 -0
- data/lib/berater/utils.rb +9 -0
- data/lib/berater/version.rb +1 -1
- data/spec/berater_spec.rb +38 -70
- data/spec/concurrency_limiter_spec.rb +59 -53
- data/spec/dsl_refinement_spec.rb +0 -12
- data/spec/dsl_spec.rb +5 -17
- data/spec/inhibitor_spec.rb +10 -5
- data/spec/limiter_set_spec.rb +173 -0
- data/spec/limiter_spec.rb +125 -10
- data/spec/matchers_spec.rb +21 -85
- data/spec/middleware_spec.rb +110 -0
- data/spec/rate_limiter_spec.rb +88 -38
- data/spec/riddle_spec.rb +6 -2
- data/spec/static_limiter_spec.rb +79 -0
- data/spec/test_mode_spec.rb +32 -109
- data/spec/unlimiter_spec.rb +11 -5
- metadata +24 -2
data/lib/berater/rspec.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'berater'
|
2
2
|
require 'berater/rspec/matchers'
|
3
3
|
require 'berater/test_mode'
|
4
|
-
require 'rspec'
|
4
|
+
require 'rspec/core'
|
5
5
|
|
6
6
|
RSpec.configure do |config|
|
7
7
|
config.include(Berater::Matchers)
|
8
8
|
|
9
9
|
config.after do
|
10
10
|
Berater.expunge rescue nil
|
11
|
-
Berater.redis.script(:flush) rescue nil
|
12
11
|
Berater.reset
|
13
12
|
end
|
14
13
|
end
|
@@ -1,10 +1,6 @@
|
|
1
1
|
module Berater
|
2
2
|
module Matchers
|
3
3
|
class Overloaded
|
4
|
-
def initialize(type)
|
5
|
-
@type = type
|
6
|
-
end
|
7
|
-
|
8
4
|
def supports_block_expectations?
|
9
5
|
true
|
10
6
|
end
|
@@ -12,13 +8,13 @@ module Berater
|
|
12
8
|
def matches?(obj)
|
13
9
|
case obj
|
14
10
|
when Proc
|
15
|
-
# eg. expect { ... }.to
|
11
|
+
# eg. expect { ... }.to be_overloaded
|
16
12
|
res = obj.call
|
17
13
|
|
18
14
|
if res.is_a? Berater::Limiter
|
19
15
|
# eg. expect { Berater.new(...) }.to be_overloaded
|
20
16
|
@limiter = res
|
21
|
-
|
17
|
+
@limiter.utilization >= 1
|
22
18
|
else
|
23
19
|
# eg. expect { Berater(...) }.to be_overloaded
|
24
20
|
# eg. expect { limiter.limit }.to be_overloaded
|
@@ -27,55 +23,39 @@ module Berater
|
|
27
23
|
when Berater::Limiter
|
28
24
|
# eg. expect(Berater.new(...)).to be_overloaded
|
29
25
|
@limiter = obj
|
30
|
-
|
26
|
+
@limiter.utilization >= 1
|
31
27
|
end
|
32
|
-
rescue
|
28
|
+
rescue Berater::Overloaded
|
33
29
|
true
|
34
30
|
end
|
35
31
|
|
36
32
|
def description
|
37
33
|
if @limiter
|
38
|
-
"be
|
34
|
+
"be overloaded"
|
39
35
|
else
|
40
|
-
"raise #{
|
36
|
+
"raise #{Berater::Overloaded}"
|
41
37
|
end
|
42
38
|
end
|
43
39
|
|
44
40
|
def failure_message
|
45
41
|
if @limiter
|
46
|
-
"expected to be
|
42
|
+
"expected to be overloaded"
|
47
43
|
else
|
48
|
-
"expected #{
|
44
|
+
"expected #{Berater::Overloaded} to be raised"
|
49
45
|
end
|
50
46
|
end
|
51
47
|
|
52
48
|
def failure_message_when_negated
|
53
49
|
if @limiter
|
54
|
-
"expected not to be
|
50
|
+
"expected not to be overloaded"
|
55
51
|
else
|
56
|
-
"did not expect #{
|
52
|
+
"did not expect #{Berater::Overloaded} to be raised"
|
57
53
|
end
|
58
54
|
end
|
59
|
-
|
60
|
-
private def verb
|
61
|
-
@type.to_s.split('::')[-1].downcase
|
62
|
-
end
|
63
55
|
end
|
64
56
|
|
65
57
|
def be_overloaded
|
66
|
-
Overloaded.new
|
67
|
-
end
|
68
|
-
|
69
|
-
def be_overrated
|
70
|
-
Overloaded.new(Berater::RateLimiter::Overrated)
|
71
|
-
end
|
72
|
-
|
73
|
-
def be_incapacitated
|
74
|
-
Overloaded.new(Berater::ConcurrencyLimiter::Incapacitated)
|
75
|
-
end
|
76
|
-
|
77
|
-
def be_inhibited
|
78
|
-
Overloaded.new(Berater::Inhibitor::Inhibited)
|
58
|
+
Overloaded.new
|
79
59
|
end
|
80
60
|
end
|
81
61
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Berater
|
2
|
+
class StaticLimiter < Limiter
|
3
|
+
|
4
|
+
LUA_SCRIPT = Berater::LuaScript(<<~LUA
|
5
|
+
local key = KEYS[1]
|
6
|
+
local capacity = tonumber(ARGV[1])
|
7
|
+
local cost = tonumber(ARGV[2])
|
8
|
+
|
9
|
+
local count = redis.call('GET', key) or 0
|
10
|
+
local allowed = (count + cost) <= capacity
|
11
|
+
|
12
|
+
if allowed then
|
13
|
+
count = count + cost
|
14
|
+
redis.call('SET', key, count)
|
15
|
+
end
|
16
|
+
|
17
|
+
return { tostring(count), allowed }
|
18
|
+
LUA
|
19
|
+
)
|
20
|
+
|
21
|
+
protected def acquire_lock(capacity, cost)
|
22
|
+
if cost == 0
|
23
|
+
# utilization check
|
24
|
+
count = redis.get(cache_key) || "0"
|
25
|
+
allowed = true
|
26
|
+
else
|
27
|
+
count, allowed = LUA_SCRIPT.eval(
|
28
|
+
redis, [ cache_key ], [ capacity, cost ],
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Redis returns Floats as strings to maintain precision
|
33
|
+
count = count.include?('.') ? count.to_f : count.to_i
|
34
|
+
|
35
|
+
raise Overloaded unless allowed
|
36
|
+
|
37
|
+
release_fn = if cost > 0
|
38
|
+
proc { redis.incrbyfloat(cache_key, -cost) }
|
39
|
+
end
|
40
|
+
|
41
|
+
Lock.new(capacity, count, release_fn)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
"#<#{self.class}(#{key}: #{capacity})>"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/berater/test_mode.rb
CHANGED
@@ -1,43 +1,47 @@
|
|
1
1
|
require 'berater'
|
2
2
|
|
3
3
|
module Berater
|
4
|
-
extend self
|
5
4
|
|
6
|
-
|
5
|
+
module TestMode
|
6
|
+
attr_reader :test_mode
|
7
|
+
|
8
|
+
def test_mode=(mode)
|
9
|
+
unless [ nil, :pass, :fail ].include?(mode)
|
10
|
+
raise ArgumentError, "invalid mode: #{Berater.test_mode}"
|
11
|
+
end
|
7
12
|
|
8
|
-
|
9
|
-
unless [ nil, :pass, :fail ].include?(mode)
|
10
|
-
raise ArgumentError, "invalid mode: #{Berater.test_mode}"
|
13
|
+
@test_mode = mode
|
11
14
|
end
|
12
15
|
|
13
|
-
|
16
|
+
def reset
|
17
|
+
super
|
18
|
+
@test_mode = nil
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end || Berater::Overloaded
|
28
|
-
|
29
|
-
raise e
|
30
|
-
else
|
31
|
-
super
|
22
|
+
class Limiter
|
23
|
+
module TestMode
|
24
|
+
def acquire_lock(*)
|
25
|
+
case Berater.test_mode
|
26
|
+
when :pass
|
27
|
+
Lock.new(Float::INFINITY, 0)
|
28
|
+
when :fail
|
29
|
+
raise Overloaded
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
37
|
end
|
37
38
|
|
39
|
+
# prepend class methods
|
40
|
+
Berater.singleton_class.prepend Berater::TestMode
|
41
|
+
|
38
42
|
# stub each Limiter subclass
|
39
43
|
ObjectSpace.each_object(Class).each do |klass|
|
40
44
|
next unless klass < Berater::Limiter
|
41
45
|
|
42
|
-
klass.prepend
|
46
|
+
klass.prepend Berater::Limiter::TestMode
|
43
47
|
end
|
data/lib/berater/unlimiter.rb
CHANGED
data/lib/berater/utils.rb
CHANGED
data/lib/berater/version.rb
CHANGED
data/spec/berater_spec.rb
CHANGED
@@ -24,50 +24,33 @@ describe Berater do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
describe '.
|
28
|
-
|
29
|
-
let(:limiter) { Berater.new(:key, Float::INFINITY) }
|
27
|
+
describe '.limiters' do
|
28
|
+
subject { Berater.limiters }
|
30
29
|
|
31
|
-
|
32
|
-
expect(limiter).to be_a Berater::Unlimiter
|
33
|
-
expect(limiter.key).to be :key
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'inherits redis' do
|
37
|
-
expect(limiter.redis).to be Berater.redis
|
38
|
-
end
|
30
|
+
let(:limiter) { Berater(:key, 1) }
|
39
31
|
|
40
|
-
|
41
|
-
|
42
|
-
limiter = Berater.new(:key, Float::INFINITY, redis: redis)
|
43
|
-
expect(limiter.redis).to be redis
|
44
|
-
end
|
32
|
+
it 'provides access to predefined limiters' do
|
33
|
+
expect(Berater.limiters).to be_a Berater::LimiterSet
|
45
34
|
end
|
46
35
|
|
47
|
-
|
48
|
-
|
36
|
+
it 'resets with Berater' do
|
37
|
+
subject << limiter
|
38
|
+
is_expected.not_to be_empty
|
49
39
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
40
|
+
Berater.reset
|
41
|
+
is_expected.to be_empty
|
42
|
+
end
|
43
|
+
end
|
54
44
|
|
55
|
-
|
56
|
-
|
57
|
-
|
45
|
+
shared_examples 'a Berater' do |klass, capacity, **opts|
|
46
|
+
describe '.new' do
|
47
|
+
let(:limiter) { Berater.new(:key, capacity, **opts) }
|
58
48
|
|
59
|
-
it '
|
60
|
-
|
61
|
-
limiter = Berater.new(:key, 0, redis: redis)
|
62
|
-
expect(limiter.redis).to be redis
|
49
|
+
it 'instantiates the right class' do
|
50
|
+
expect(limiter).to be_a klass
|
63
51
|
end
|
64
|
-
end
|
65
|
-
|
66
|
-
context 'rate mode' do
|
67
|
-
let(:limiter) { Berater.new(:key, 1, :second) }
|
68
52
|
|
69
|
-
it '
|
70
|
-
expect(limiter).to be_a Berater::RateLimiter
|
53
|
+
it 'sets the key' do
|
71
54
|
expect(limiter.key).to be :key
|
72
55
|
end
|
73
56
|
|
@@ -75,60 +58,45 @@ describe Berater do
|
|
75
58
|
expect(limiter.redis).to be Berater.redis
|
76
59
|
end
|
77
60
|
|
78
|
-
it 'accepts
|
61
|
+
it 'accepts an optional redis parameter' do
|
79
62
|
redis = double('Redis')
|
80
|
-
limiter = Berater.new(:key,
|
63
|
+
limiter = Berater.new(:key, capacity, opts.merge(redis: redis))
|
81
64
|
expect(limiter.redis).to be redis
|
82
65
|
end
|
83
66
|
end
|
84
67
|
|
85
|
-
|
86
|
-
let(:limiter) { Berater
|
68
|
+
describe 'Berater() convenience method' do
|
69
|
+
let(:limiter) { Berater(:key, capacity, **opts) }
|
87
70
|
|
88
|
-
it '
|
89
|
-
expect(limiter).to be_a
|
90
|
-
expect(limiter.key).to be :key
|
71
|
+
it 'creates a limiter' do
|
72
|
+
expect(limiter).to be_a klass
|
91
73
|
end
|
92
74
|
|
93
|
-
it '
|
94
|
-
expect(limiter
|
75
|
+
it 'creates an equivalent limiter' do
|
76
|
+
expect(limiter).to eq Berater.new(:key, capacity, **opts)
|
95
77
|
end
|
96
78
|
|
97
|
-
|
98
|
-
|
99
|
-
limiter = Berater.new(:key, 1, redis: redis)
|
100
|
-
expect(limiter.redis).to be redis
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
79
|
+
context 'with a block' do
|
80
|
+
before { Berater.test_mode = :pass }
|
104
81
|
|
105
|
-
|
106
|
-
RSpec.shared_examples 'test convenience' do |klass, *args|
|
107
|
-
it 'creates a limiter' do
|
108
|
-
limiter = Berater(:key, *args)
|
109
|
-
expect(limiter).to be_a klass
|
110
|
-
end
|
82
|
+
subject { Berater(:key, capacity, **opts) { 123 } }
|
111
83
|
|
112
|
-
context 'with a block' do
|
113
84
|
it 'creates a limiter and calls limit' do
|
114
|
-
limiter = Berater(:key, *args)
|
115
85
|
expect(klass).to receive(:new).and_return(limiter)
|
116
86
|
expect(limiter).to receive(:limit).and_call_original
|
87
|
+
subject
|
88
|
+
end
|
117
89
|
|
118
|
-
|
119
|
-
|
120
|
-
expect(res).to be true
|
121
|
-
rescue Berater::Overloaded
|
122
|
-
expect(klass).to be Berater::Inhibitor
|
123
|
-
end
|
90
|
+
it 'yields' do
|
91
|
+
is_expected.to be 123
|
124
92
|
end
|
125
93
|
end
|
126
94
|
end
|
127
|
-
|
128
|
-
include_examples 'test convenience', Berater::Unlimiter, Float::INFINITY
|
129
|
-
include_examples 'test convenience', Berater::Inhibitor, 0
|
130
|
-
include_examples 'test convenience', Berater::RateLimiter, 1, :second
|
131
|
-
include_examples 'test convenience', Berater::ConcurrencyLimiter, 1
|
132
95
|
end
|
133
96
|
|
97
|
+
include_examples 'a Berater', Berater::ConcurrencyLimiter, 1, timeout: 1
|
98
|
+
include_examples 'a Berater', Berater::Inhibitor, 0
|
99
|
+
include_examples 'a Berater', Berater::RateLimiter, 1, interval: :second
|
100
|
+
include_examples 'a Berater', Berater::StaticLimiter, 1
|
101
|
+
include_examples 'a Berater', Berater::Unlimiter, Float::INFINITY
|
134
102
|
end
|
@@ -18,7 +18,7 @@ describe Berater::ConcurrencyLimiter do
|
|
18
18
|
describe '#capacity' do
|
19
19
|
def expect_capacity(capacity)
|
20
20
|
limiter = described_class.new(:key, capacity)
|
21
|
-
expect(limiter.capacity).to eq capacity
|
21
|
+
expect(limiter.capacity).to eq capacity.to_i
|
22
22
|
end
|
23
23
|
|
24
24
|
it { expect_capacity(0) }
|
@@ -36,22 +36,28 @@ describe Berater::ConcurrencyLimiter do
|
|
36
36
|
it { expect_bad_capacity(-1) }
|
37
37
|
it { expect_bad_capacity('1') }
|
38
38
|
it { expect_bad_capacity(:one) }
|
39
|
+
it { expect_bad_capacity(Float::INFINITY) }
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
43
|
describe '#timeout' do
|
43
44
|
# see spec/utils_spec.rb
|
44
45
|
|
46
|
+
it 'defaults to nil' do
|
47
|
+
limiter = described_class.new(:key, 1)
|
48
|
+
expect(limiter.timeout).to be nil
|
49
|
+
end
|
50
|
+
|
45
51
|
it 'saves the interval in original and millisecond format' do
|
46
52
|
limiter = described_class.new(:key, 1, timeout: 3)
|
47
53
|
expect(limiter.timeout).to be 3
|
48
|
-
expect(limiter.instance_variable_get(:@
|
54
|
+
expect(limiter.instance_variable_get(:@timeout)).to be (3 * 10**3)
|
49
55
|
end
|
50
56
|
|
51
57
|
it 'handles infinity' do
|
52
58
|
limiter = described_class.new(:key, 1, timeout: Float::INFINITY)
|
53
59
|
expect(limiter.timeout).to be Float::INFINITY
|
54
|
-
expect(limiter.instance_variable_get(:@
|
60
|
+
expect(limiter.instance_variable_get(:@timeout)).to be 0
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
@@ -77,14 +83,14 @@ describe Berater::ConcurrencyLimiter do
|
|
77
83
|
expect(limiter.limit).to be_a Berater::Lock
|
78
84
|
expect(limiter.limit).to be_a Berater::Lock
|
79
85
|
|
80
|
-
expect(limiter).to
|
86
|
+
expect(limiter).to be_overloaded
|
81
87
|
end
|
82
88
|
|
83
89
|
context 'with capacity 0' do
|
84
90
|
let(:limiter) { described_class.new(:key, 0) }
|
85
91
|
|
86
92
|
it 'always fails' do
|
87
|
-
expect(limiter).to
|
93
|
+
expect(limiter).to be_overloaded
|
88
94
|
end
|
89
95
|
end
|
90
96
|
|
@@ -96,80 +102,80 @@ describe Berater::ConcurrencyLimiter do
|
|
96
102
|
|
97
103
|
# since fractional cost is not supported
|
98
104
|
expect(lock.capacity).to be 1
|
99
|
-
expect(limiter).to
|
105
|
+
expect(limiter).to be_overloaded
|
100
106
|
end
|
101
107
|
end
|
102
108
|
|
103
109
|
it 'limit resets over time' do
|
104
110
|
2.times { limiter.limit }
|
105
|
-
expect(limiter).to
|
111
|
+
expect(limiter).to be_overloaded
|
106
112
|
|
107
113
|
Timecop.freeze(30)
|
108
114
|
|
109
115
|
2.times { limiter.limit }
|
110
|
-
expect(limiter).to
|
116
|
+
expect(limiter).to be_overloaded
|
111
117
|
end
|
112
118
|
|
113
119
|
it 'limit resets with millisecond precision' do
|
114
120
|
2.times { limiter.limit }
|
115
|
-
expect(limiter).to
|
121
|
+
expect(limiter).to be_overloaded
|
116
122
|
|
117
123
|
# travel forward to just before first lock times out
|
118
124
|
Timecop.freeze(29.999)
|
119
|
-
expect(limiter).to
|
125
|
+
expect(limiter).to be_overloaded
|
120
126
|
|
121
127
|
# traveling one more millisecond will decrement the count
|
122
128
|
Timecop.freeze(0.001)
|
123
129
|
2.times { limiter.limit }
|
124
|
-
expect(limiter).to
|
130
|
+
expect(limiter).to be_overloaded
|
125
131
|
end
|
126
132
|
|
127
133
|
it 'accepts a dynamic capacity' do
|
128
|
-
expect { limiter.limit(capacity: 0) }.to
|
134
|
+
expect { limiter.limit(capacity: 0) }.to be_overloaded
|
129
135
|
5.times { limiter.limit(capacity: 10) }
|
130
|
-
expect { limiter }.to
|
136
|
+
expect { limiter }.to be_overloaded
|
131
137
|
end
|
132
138
|
|
133
139
|
context 'with cost parameter' do
|
134
|
-
it { expect { limiter.limit(cost: 4) }.to
|
140
|
+
it { expect { limiter.limit(cost: 4) }.to be_overloaded }
|
135
141
|
|
136
142
|
it 'works within limit' do
|
137
143
|
limiter.limit(cost: 2)
|
138
|
-
expect(limiter).to
|
144
|
+
expect(limiter).to be_overloaded
|
139
145
|
end
|
140
146
|
|
141
147
|
it 'releases full cost' do
|
142
148
|
lock = limiter.limit(cost: 2)
|
143
|
-
expect(limiter).to
|
149
|
+
expect(limiter).to be_overloaded
|
144
150
|
|
145
151
|
lock.release
|
146
|
-
expect(limiter).not_to
|
152
|
+
expect(limiter).not_to be_overloaded
|
147
153
|
|
148
154
|
lock = limiter.limit(cost: 2)
|
149
|
-
expect(limiter).to
|
155
|
+
expect(limiter).to be_overloaded
|
150
156
|
end
|
151
157
|
|
152
158
|
it 'respects timeout' do
|
153
159
|
limiter.limit(cost: 2)
|
154
|
-
expect(limiter).to
|
160
|
+
expect(limiter).to be_overloaded
|
155
161
|
|
156
162
|
Timecop.freeze(30)
|
157
|
-
expect(limiter).not_to
|
163
|
+
expect(limiter).not_to be_overloaded
|
158
164
|
|
159
165
|
limiter.limit(cost: 2)
|
160
|
-
expect(limiter).to
|
166
|
+
expect(limiter).to be_overloaded
|
161
167
|
end
|
162
168
|
|
163
169
|
context 'with fractional costs' do
|
164
170
|
it 'rounds up' do
|
165
171
|
limiter.limit(cost: 1.5)
|
166
|
-
expect(limiter).to
|
172
|
+
expect(limiter).to be_overloaded
|
167
173
|
end
|
168
174
|
|
169
175
|
it 'accumulates correctly' do
|
170
176
|
limiter.limit(cost: 0.5) # => 1
|
171
177
|
limiter.limit(cost: 0.7) # => 2
|
172
|
-
expect(limiter).to
|
178
|
+
expect(limiter).to be_overloaded
|
173
179
|
end
|
174
180
|
end
|
175
181
|
|
@@ -187,8 +193,8 @@ describe Berater::ConcurrencyLimiter do
|
|
187
193
|
it 'works as expected' do
|
188
194
|
expect(limiter_one.limit).to be_a Berater::Lock
|
189
195
|
|
190
|
-
expect(limiter_one).to
|
191
|
-
expect(limiter_two).to
|
196
|
+
expect(limiter_one).to be_overloaded
|
197
|
+
expect(limiter_two).to be_overloaded
|
192
198
|
end
|
193
199
|
end
|
194
200
|
|
@@ -202,22 +208,22 @@ describe Berater::ConcurrencyLimiter do
|
|
202
208
|
one_lock = limiter_one.limit
|
203
209
|
expect(one_lock).to be_a Berater::Lock
|
204
210
|
|
205
|
-
expect(limiter_one).to
|
206
|
-
expect(limiter_two).not_to
|
211
|
+
expect(limiter_one).to be_overloaded
|
212
|
+
expect(limiter_two).not_to be_overloaded
|
207
213
|
|
208
214
|
two_lock = limiter_two.limit
|
209
215
|
expect(two_lock).to be_a Berater::Lock
|
210
216
|
|
211
|
-
expect(limiter_one).to
|
212
|
-
expect(limiter_two).to
|
217
|
+
expect(limiter_one).to be_overloaded
|
218
|
+
expect(limiter_two).to be_overloaded
|
213
219
|
|
214
220
|
one_lock.release
|
215
|
-
expect(limiter_one).to
|
216
|
-
expect(limiter_two).not_to
|
221
|
+
expect(limiter_one).to be_overloaded
|
222
|
+
expect(limiter_two).not_to be_overloaded
|
217
223
|
|
218
224
|
two_lock.release
|
219
|
-
expect(limiter_one).not_to
|
220
|
-
expect(limiter_two).not_to
|
225
|
+
expect(limiter_one).not_to be_overloaded
|
226
|
+
expect(limiter_two).not_to be_overloaded
|
221
227
|
end
|
222
228
|
end
|
223
229
|
|
@@ -226,41 +232,41 @@ describe Berater::ConcurrencyLimiter do
|
|
226
232
|
let(:limiter_two) { described_class.new(:two, 1) }
|
227
233
|
|
228
234
|
it 'works as expected' do
|
229
|
-
expect(limiter_one).not_to
|
230
|
-
expect(limiter_two).not_to
|
235
|
+
expect(limiter_one).not_to be_overloaded
|
236
|
+
expect(limiter_two).not_to be_overloaded
|
231
237
|
|
232
238
|
one_lock = limiter_one.limit
|
233
239
|
expect(one_lock).to be_a Berater::Lock
|
234
240
|
|
235
|
-
expect(limiter_one).to
|
236
|
-
expect(limiter_two).not_to
|
241
|
+
expect(limiter_one).to be_overloaded
|
242
|
+
expect(limiter_two).not_to be_overloaded
|
237
243
|
|
238
244
|
two_lock = limiter_two.limit
|
239
245
|
expect(two_lock).to be_a Berater::Lock
|
240
246
|
|
241
|
-
expect(limiter_one).to
|
242
|
-
expect(limiter_two).to
|
247
|
+
expect(limiter_one).to be_overloaded
|
248
|
+
expect(limiter_two).to be_overloaded
|
243
249
|
end
|
244
250
|
end
|
245
251
|
end
|
246
252
|
|
247
|
-
describe '#
|
248
|
-
let(:limiter) { described_class.new(:key,
|
253
|
+
describe '#utilization' do
|
254
|
+
let(:limiter) { described_class.new(:key, 10, timeout: 30) }
|
249
255
|
|
250
256
|
it 'works' do
|
251
|
-
expect(limiter.
|
252
|
-
lock = limiter.limit
|
253
|
-
expect(limiter.overloaded?).to be true
|
254
|
-
lock.release
|
255
|
-
expect(limiter.overloaded?).to be false
|
256
|
-
end
|
257
|
+
expect(limiter.utilization).to be 0.0
|
257
258
|
|
258
|
-
|
259
|
-
expect(limiter.
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
259
|
+
2.times { limiter.limit }
|
260
|
+
expect(limiter.utilization).to be 0.2
|
261
|
+
|
262
|
+
Timecop.freeze(15)
|
263
|
+
|
264
|
+
8.times { limiter.limit }
|
265
|
+
expect(limiter.utilization).to be 1.0
|
266
|
+
|
267
|
+
Timecop.freeze(15)
|
268
|
+
|
269
|
+
expect(limiter.utilization).to be 0.8
|
264
270
|
end
|
265
271
|
end
|
266
272
|
|