berater 0.7.1 → 0.8.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 +6 -8
- data/lib/berater/concurrency_limiter.rb +9 -13
- data/lib/berater/inhibitor.rb +4 -0
- data/lib/berater/limiter.rb +16 -6
- data/lib/berater/lock.rb +2 -1
- data/lib/berater/rate_limiter.rb +9 -8
- data/lib/berater/rspec.rb +0 -1
- data/lib/berater/static_limiter.rb +49 -0
- data/lib/berater/test_mode.rb +27 -16
- 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 +26 -76
- data/spec/concurrency_limiter_spec.rb +8 -2
- data/spec/inhibitor_spec.rb +10 -5
- data/spec/limiter_spec.rb +56 -6
- data/spec/rate_limiter_spec.rb +2 -1
- data/spec/riddle_spec.rb +1 -1
- data/spec/static_limiter_spec.rb +79 -0
- data/spec/test_mode_spec.rb +12 -6
- data/spec/unlimiter_spec.rb +11 -5
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c32c1de752ccc7a2a71e4628b99f3561db623374dabaf95af39aadcc3b1a2d16
|
4
|
+
data.tar.gz: 33019954de1d79522ed7105e9f0a7da3a204df74cacd25aa4b57e749a81047bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89460a9a81a8d384addb095c425da0eed4fdbe0e15ecd85ee97ffcf4292441deeb34c7e4cd4591adca06096a17f7c131b79c6474d9efb2b635fbe398ad68d00e
|
7
|
+
data.tar.gz: 0a331b939e4c19817c42a8aa8fc5be7fd59d63f6becd5fe6ed1c226690d1524ef040a28d8bcb625d75452ab2f5c8539ddf69093bbd30c4ba9e3353259a3e568d
|
data/lib/berater.rb
CHANGED
@@ -31,8 +31,10 @@ module Berater
|
|
31
31
|
if opts[:interval]
|
32
32
|
args << opts.delete(:interval)
|
33
33
|
Berater::RateLimiter
|
34
|
-
|
34
|
+
elsif opts[:timeout]
|
35
35
|
Berater::ConcurrencyLimiter
|
36
|
+
else
|
37
|
+
Berater::StaticLimiter
|
36
38
|
end
|
37
39
|
end.yield_self do |klass|
|
38
40
|
args = [ key, capacity, *args ].compact
|
@@ -49,17 +51,13 @@ module Berater
|
|
49
51
|
end
|
50
52
|
|
51
53
|
# convenience method
|
52
|
-
def Berater(
|
53
|
-
|
54
|
-
if block_given?
|
55
|
-
limiter.limit(&block)
|
56
|
-
else
|
57
|
-
limiter
|
58
|
-
end
|
54
|
+
def Berater(*args, **opts, &block)
|
55
|
+
Berater::Utils.convenience_fn(Berater, *args, **opts, &block)
|
59
56
|
end
|
60
57
|
|
61
58
|
# load limiters
|
62
59
|
require 'berater/concurrency_limiter'
|
63
60
|
require 'berater/inhibitor'
|
64
61
|
require 'berater/rate_limiter'
|
62
|
+
require 'berater/static_limiter'
|
65
63
|
require 'berater/unlimiter'
|
@@ -1,21 +1,22 @@
|
|
1
1
|
module Berater
|
2
2
|
class ConcurrencyLimiter < Limiter
|
3
3
|
|
4
|
-
attr_reader :timeout
|
5
|
-
|
6
4
|
def initialize(key, capacity, **opts)
|
7
5
|
super(key, capacity, **opts)
|
8
6
|
|
9
|
-
#
|
7
|
+
# truncate fractional capacity
|
10
8
|
self.capacity = capacity.to_i
|
11
9
|
|
12
10
|
self.timeout = opts[:timeout] || 0
|
13
11
|
end
|
14
12
|
|
13
|
+
def timeout
|
14
|
+
options[:timeout]
|
15
|
+
end
|
16
|
+
|
15
17
|
private def timeout=(timeout)
|
16
|
-
@timeout = timeout
|
17
18
|
timeout = 0 if timeout == Float::INFINITY
|
18
|
-
@
|
19
|
+
@timeout = Berater::Utils.to_msec(timeout)
|
19
20
|
end
|
20
21
|
|
21
22
|
LUA_SCRIPT = Berater::LuaScript(<<~LUA
|
@@ -72,24 +73,19 @@ module Berater
|
|
72
73
|
|
73
74
|
count, *lock_ids = LUA_SCRIPT.eval(
|
74
75
|
redis,
|
75
|
-
[ cache_key
|
76
|
-
[ capacity, ts, @
|
76
|
+
[ cache_key, self.class.cache_key('lock_id') ],
|
77
|
+
[ capacity, ts, @timeout, cost ]
|
77
78
|
)
|
78
79
|
|
79
80
|
raise Overloaded if lock_ids.empty?
|
80
81
|
|
81
82
|
release_fn = if cost > 0
|
82
|
-
proc {
|
83
|
+
proc { redis.zrem(cache_key, lock_ids) }
|
83
84
|
end
|
84
85
|
|
85
86
|
Lock.new(capacity, count, release_fn)
|
86
87
|
end
|
87
88
|
|
88
|
-
private def release(lock_ids)
|
89
|
-
res = redis.zrem(cache_key(key), lock_ids)
|
90
|
-
res == true || res == lock_ids.count # depending on which version of Redis
|
91
|
-
end
|
92
|
-
|
93
89
|
def to_s
|
94
90
|
"#<#{self.class}(#{key}: #{capacity} at a time)>"
|
95
91
|
end
|
data/lib/berater/inhibitor.rb
CHANGED
data/lib/berater/limiter.rb
CHANGED
@@ -43,10 +43,6 @@ module Berater
|
|
43
43
|
1.0
|
44
44
|
end
|
45
45
|
|
46
|
-
def to_s
|
47
|
-
"#<#{self.class}>"
|
48
|
-
end
|
49
|
-
|
50
46
|
def ==(other)
|
51
47
|
self.class == other.class &&
|
52
48
|
self.key == other.key &&
|
@@ -92,8 +88,22 @@ module Berater
|
|
92
88
|
raise NotImplementedError
|
93
89
|
end
|
94
90
|
|
95
|
-
def cache_key(
|
96
|
-
"#{
|
91
|
+
def cache_key(subkey = nil)
|
92
|
+
instance_key = subkey.nil? ? key : "#{key}:#{subkey}"
|
93
|
+
self.class.cache_key(instance_key)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.cache_key(key)
|
97
|
+
klass = to_s.split(':')[-1]
|
98
|
+
"Berater:#{klass}:#{key}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.inherited(subclass)
|
102
|
+
# automagically create convenience method
|
103
|
+
name = subclass.to_s.split(':')[-1]
|
104
|
+
Berater.define_singleton_method(name) do |*args, **opts, &block|
|
105
|
+
Berater::Utils.convenience_fn(subclass, *args, **opts, &block)
|
106
|
+
end
|
97
107
|
end
|
98
108
|
|
99
109
|
end
|
data/lib/berater/lock.rb
CHANGED
data/lib/berater/rate_limiter.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
module Berater
|
2
2
|
class RateLimiter < Limiter
|
3
3
|
|
4
|
-
attr_accessor :interval
|
5
|
-
|
6
4
|
def initialize(key, capacity, interval, **opts)
|
5
|
+
super(key, capacity, interval, **opts)
|
7
6
|
self.interval = interval
|
8
|
-
|
7
|
+
end
|
8
|
+
|
9
|
+
def interval
|
10
|
+
args[0]
|
9
11
|
end
|
10
12
|
|
11
13
|
private def interval=(interval)
|
12
|
-
@interval = interval
|
13
|
-
@interval_msec = Berater::Utils.to_msec(interval)
|
14
|
+
@interval = Berater::Utils.to_msec(interval)
|
14
15
|
|
15
|
-
unless @
|
16
|
+
unless @interval > 0
|
16
17
|
raise ArgumentError, 'interval must be > 0'
|
17
18
|
end
|
18
19
|
end
|
@@ -76,8 +77,8 @@ module Berater
|
|
76
77
|
|
77
78
|
count, allowed = LUA_SCRIPT.eval(
|
78
79
|
redis,
|
79
|
-
[ cache_key
|
80
|
-
[ ts, capacity, @
|
80
|
+
[ cache_key ],
|
81
|
+
[ ts, capacity, @interval, cost ]
|
81
82
|
)
|
82
83
|
|
83
84
|
count = count.include?('.') ? count.to_f : count.to_i
|
data/lib/berater/rspec.rb
CHANGED
@@ -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,36 +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
|
-
|
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
|
25
33
|
end
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
29
37
|
end
|
30
38
|
|
39
|
+
# prepend class methods
|
40
|
+
Berater.singleton_class.prepend Berater::TestMode
|
41
|
+
|
31
42
|
# stub each Limiter subclass
|
32
43
|
ObjectSpace.each_object(Class).each do |klass|
|
33
44
|
next unless klass < Berater::Limiter
|
34
45
|
|
35
|
-
klass.prepend
|
46
|
+
klass.prepend Berater::Limiter::TestMode
|
36
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,15 @@ describe Berater do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
let(:limiter) { Berater.new(:key,
|
27
|
+
shared_examples 'a Berater' do |klass, capacity, **opts|
|
28
|
+
describe '.new' do
|
29
|
+
let(:limiter) { Berater.new(:key, capacity, **opts) }
|
30
30
|
|
31
|
-
it 'instantiates
|
32
|
-
expect(limiter).to be_a
|
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
|
39
|
-
|
40
|
-
it 'accepts options' do
|
41
|
-
redis = double('Redis')
|
42
|
-
limiter = Berater.new(:key, Float::INFINITY, redis: redis)
|
43
|
-
expect(limiter.redis).to be redis
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
context 'Inhibitor mode' do
|
48
|
-
let(:limiter) { Berater.new(:key, 0) }
|
49
|
-
|
50
|
-
it 'instantiates an Inhibitor' do
|
51
|
-
expect(limiter).to be_a Berater::Inhibitor
|
52
|
-
expect(limiter.key).to be :key
|
53
|
-
end
|
54
|
-
|
55
|
-
it 'inherits redis' do
|
56
|
-
expect(limiter.redis).to be Berater.redis
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'accepts options' do
|
60
|
-
redis = double('Redis')
|
61
|
-
limiter = Berater.new(:key, 0, redis: redis)
|
62
|
-
expect(limiter.redis).to be redis
|
31
|
+
it 'instantiates the right class' do
|
32
|
+
expect(limiter).to be_a klass
|
63
33
|
end
|
64
|
-
end
|
65
34
|
|
66
|
-
|
67
|
-
let(:limiter) { Berater.new(:key, 1, interval: :second) }
|
68
|
-
|
69
|
-
it 'instantiates a RateLimiter' do
|
70
|
-
expect(limiter).to be_a Berater::RateLimiter
|
35
|
+
it 'sets the key' do
|
71
36
|
expect(limiter.key).to be :key
|
72
37
|
end
|
73
38
|
|
@@ -75,60 +40,45 @@ describe Berater do
|
|
75
40
|
expect(limiter.redis).to be Berater.redis
|
76
41
|
end
|
77
42
|
|
78
|
-
it 'accepts
|
43
|
+
it 'accepts an optional redis parameter' do
|
79
44
|
redis = double('Redis')
|
80
|
-
limiter = Berater.new(:key,
|
45
|
+
limiter = Berater.new(:key, capacity, opts.merge(redis: redis))
|
81
46
|
expect(limiter.redis).to be redis
|
82
47
|
end
|
83
48
|
end
|
84
49
|
|
85
|
-
|
86
|
-
let(:limiter) { Berater
|
50
|
+
describe 'Berater() convenience method' do
|
51
|
+
let(:limiter) { Berater(:key, capacity, **opts) }
|
87
52
|
|
88
|
-
it '
|
89
|
-
expect(limiter).to be_a
|
90
|
-
expect(limiter.key).to be :key
|
53
|
+
it 'creates a limiter' do
|
54
|
+
expect(limiter).to be_a klass
|
91
55
|
end
|
92
56
|
|
93
|
-
it '
|
94
|
-
expect(limiter
|
57
|
+
it 'creates an equivalent limiter' do
|
58
|
+
expect(limiter).to eq Berater.new(:key, capacity, **opts)
|
95
59
|
end
|
96
60
|
|
97
|
-
|
98
|
-
|
99
|
-
limiter = Berater.new(:key, 1, redis: redis)
|
100
|
-
expect(limiter.redis).to be redis
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
61
|
+
context 'with a block' do
|
62
|
+
before { Berater.test_mode = :pass }
|
104
63
|
|
105
|
-
|
106
|
-
RSpec.shared_examples 'test convenience' do |klass, capacity, **opts|
|
107
|
-
it 'creates a limiter' do
|
108
|
-
limiter = Berater(:key, capacity, **opts)
|
109
|
-
expect(limiter).to be_a klass
|
110
|
-
end
|
64
|
+
subject { Berater(:key, capacity, **opts) { 123 } }
|
111
65
|
|
112
|
-
context 'with a block' do
|
113
66
|
it 'creates a limiter and calls limit' do
|
114
|
-
limiter = Berater(:key, capacity, **opts)
|
115
67
|
expect(klass).to receive(:new).and_return(limiter)
|
116
68
|
expect(limiter).to receive(:limit).and_call_original
|
69
|
+
subject
|
70
|
+
end
|
117
71
|
|
118
|
-
|
119
|
-
|
120
|
-
expect(res).to be true
|
121
|
-
rescue Berater::Overloaded
|
122
|
-
expect(klass).to be Berater::Inhibitor
|
123
|
-
end
|
72
|
+
it 'yields' do
|
73
|
+
is_expected.to be 123
|
124
74
|
end
|
125
75
|
end
|
126
76
|
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, interval: :second
|
131
|
-
include_examples 'test convenience', Berater::ConcurrencyLimiter, 1
|
132
77
|
end
|
133
78
|
|
79
|
+
include_examples 'a Berater', Berater::ConcurrencyLimiter, 1, timeout: 1
|
80
|
+
include_examples 'a Berater', Berater::Inhibitor, 0
|
81
|
+
include_examples 'a Berater', Berater::RateLimiter, 1, interval: :second
|
82
|
+
include_examples 'a Berater', Berater::StaticLimiter, 1
|
83
|
+
include_examples 'a Berater', Berater::Unlimiter, Float::INFINITY
|
134
84
|
end
|
@@ -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
|
|
data/spec/inhibitor_spec.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
describe Berater::Inhibitor do
|
2
|
+
subject { described_class.new }
|
3
|
+
|
2
4
|
describe '.new' do
|
3
5
|
it 'initializes without any arguments or options' do
|
4
|
-
|
6
|
+
is_expected.to be_a described_class
|
5
7
|
end
|
6
8
|
|
7
9
|
it 'initializes with any arguments and options' do
|
@@ -9,15 +11,18 @@ describe Berater::Inhibitor do
|
|
9
11
|
end
|
10
12
|
|
11
13
|
it 'has default values' do
|
12
|
-
expect(
|
13
|
-
expect(
|
14
|
+
expect(subject.key).to be :inhibitor
|
15
|
+
expect(subject.redis).to be Berater.redis
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
19
|
describe '#limit' do
|
18
|
-
subject { described_class.new }
|
19
|
-
|
20
20
|
it_behaves_like 'it is overloaded'
|
21
21
|
end
|
22
22
|
|
23
|
+
describe '#to_s' do
|
24
|
+
it do
|
25
|
+
expect(subject.to_s).to include described_class.to_s
|
26
|
+
end
|
27
|
+
end
|
23
28
|
end
|
data/spec/limiter_spec.rb
CHANGED
@@ -44,6 +44,10 @@ describe Berater::Limiter do
|
|
44
44
|
expect {
|
45
45
|
subject.limit(cost: -1)
|
46
46
|
}.to raise_error(ArgumentError)
|
47
|
+
|
48
|
+
expect {
|
49
|
+
subject.limit(cost: Float::INFINITY)
|
50
|
+
}.to raise_error(ArgumentError)
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -61,12 +65,6 @@ describe Berater::Limiter do
|
|
61
65
|
)
|
62
66
|
end
|
63
67
|
|
64
|
-
it 'equals something with equvalent initialization parameters' do
|
65
|
-
expect(limiter).to eq(
|
66
|
-
Berater::RateLimiter.new(:key, 1, 1)
|
67
|
-
)
|
68
|
-
end
|
69
|
-
|
70
68
|
it 'does not equal something different' do
|
71
69
|
expect(limiter).not_to eq(
|
72
70
|
Berater::RateLimiter.new(:key, 2, :second)
|
@@ -104,4 +102,56 @@ describe Berater::Limiter do
|
|
104
102
|
end
|
105
103
|
end
|
106
104
|
|
105
|
+
describe '#cache_key' do
|
106
|
+
subject { klass.new(:key).send(:cache_key) }
|
107
|
+
|
108
|
+
context 'with Unlimiter' do
|
109
|
+
let(:klass) { Berater::Unlimiter }
|
110
|
+
|
111
|
+
it do
|
112
|
+
is_expected.to eq 'Berater:Unlimiter:key'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'with custom limiter' do
|
117
|
+
MyLimiter = Class.new(Berater::Unlimiter)
|
118
|
+
|
119
|
+
let(:klass) { MyLimiter }
|
120
|
+
|
121
|
+
it 'adds Berater prefix' do
|
122
|
+
is_expected.to eq 'Berater:MyLimiter:key'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '.cache_key' do
|
128
|
+
subject { klass.send(:cache_key, :key) }
|
129
|
+
|
130
|
+
context 'with Unlimiter' do
|
131
|
+
let(:klass) { Berater::Unlimiter }
|
132
|
+
|
133
|
+
it do
|
134
|
+
is_expected.to eq 'Berater:Unlimiter:key'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'with custom limiter' do
|
139
|
+
MyLimiter = Class.new(Berater::Unlimiter)
|
140
|
+
|
141
|
+
let(:klass) { MyLimiter }
|
142
|
+
|
143
|
+
it 'adds Berater prefix' do
|
144
|
+
is_expected.to eq 'Berater:MyLimiter:key'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '.inherited' do
|
150
|
+
it 'creates convenience methods' do
|
151
|
+
expect(Berater.method(:Unlimiter)).to be_a Method
|
152
|
+
expect(Berater::Unlimiter()).to be_a Berater::Unlimiter
|
153
|
+
expect {|b| Berater::Unlimiter(&b) }.to yield_control
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
107
157
|
end
|
data/spec/rate_limiter_spec.rb
CHANGED
@@ -37,6 +37,7 @@ describe Berater::RateLimiter do
|
|
37
37
|
it { expect_bad_capacity(-1) }
|
38
38
|
it { expect_bad_capacity('1') }
|
39
39
|
it { expect_bad_capacity(:one) }
|
40
|
+
it { expect_bad_capacity(Float::INFINITY) }
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -47,7 +48,7 @@ describe Berater::RateLimiter do
|
|
47
48
|
|
48
49
|
it 'saves the interval in original and millisecond format' do
|
49
50
|
expect(subject.interval).to be :second
|
50
|
-
expect(subject.instance_variable_get(:@
|
51
|
+
expect(subject.instance_variable_get(:@interval)).to be 10**3
|
51
52
|
end
|
52
53
|
|
53
54
|
it 'must be > 0' do
|
data/spec/riddle_spec.rb
CHANGED
@@ -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
|
data/spec/test_mode_spec.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
1
|
describe Berater::TestMode do
|
2
|
-
after do
|
3
|
-
Berater.test_mode = nil
|
4
|
-
end
|
5
|
-
|
6
2
|
context 'after test_mode.rb has been loaded' do
|
7
3
|
it 'monkey patches Berater' do
|
8
4
|
expect(Berater).to respond_to(:test_mode)
|
@@ -13,8 +9,8 @@ describe Berater::TestMode do
|
|
13
9
|
end
|
14
10
|
|
15
11
|
it 'prepends Limiter subclasses' do
|
16
|
-
expect(Berater::Unlimiter.ancestors).to include(
|
17
|
-
expect(Berater::Inhibitor.ancestors).to include(
|
12
|
+
expect(Berater::Unlimiter.ancestors).to include(Berater::Limiter::TestMode)
|
13
|
+
expect(Berater::Inhibitor.ancestors).to include(Berater::Limiter::TestMode)
|
18
14
|
end
|
19
15
|
|
20
16
|
it 'preserves the original functionality via super' do
|
@@ -55,6 +51,16 @@ describe Berater::TestMode do
|
|
55
51
|
end
|
56
52
|
end
|
57
53
|
|
54
|
+
describe '.reset' do
|
55
|
+
before { Berater.test_mode = :pass }
|
56
|
+
|
57
|
+
it 'resets test_mode' do
|
58
|
+
expect(Berater.test_mode).to be :pass
|
59
|
+
Berater.reset
|
60
|
+
expect(Berater.test_mode).to be nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
58
64
|
shared_examples 'it supports test_mode' do
|
59
65
|
before do
|
60
66
|
# without hitting Redis
|
data/spec/unlimiter_spec.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
describe Berater::Unlimiter do
|
2
|
+
subject { described_class.new }
|
3
|
+
|
2
4
|
it_behaves_like 'a limiter', described_class.new
|
3
5
|
|
4
6
|
describe '.new' do
|
5
7
|
it 'initializes without any arguments or options' do
|
6
|
-
|
8
|
+
is_expected.to be_a described_class
|
7
9
|
end
|
8
10
|
|
9
11
|
it 'initializes with any arguments and options' do
|
@@ -11,14 +13,12 @@ describe Berater::Unlimiter do
|
|
11
13
|
end
|
12
14
|
|
13
15
|
it 'has default values' do
|
14
|
-
expect(
|
15
|
-
expect(
|
16
|
+
expect(subject.key).to be :unlimiter
|
17
|
+
expect(subject.redis).to be Berater.redis
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
19
21
|
describe '#limit' do
|
20
|
-
subject { described_class.new }
|
21
|
-
|
22
22
|
it_behaves_like 'it is not overloaded'
|
23
23
|
|
24
24
|
it 'is never overloaded' do
|
@@ -27,4 +27,10 @@ describe Berater::Unlimiter do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
describe '#to_s' do
|
32
|
+
it do
|
33
|
+
expect(subject.to_s).to include described_class.to_s
|
34
|
+
end
|
35
|
+
end
|
30
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: berater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Pepper
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -138,6 +138,7 @@ files:
|
|
138
138
|
- lib/berater/rate_limiter.rb
|
139
139
|
- lib/berater/rspec.rb
|
140
140
|
- lib/berater/rspec/matchers.rb
|
141
|
+
- lib/berater/static_limiter.rb
|
141
142
|
- lib/berater/test_mode.rb
|
142
143
|
- lib/berater/unlimiter.rb
|
143
144
|
- lib/berater/utils.rb
|
@@ -152,6 +153,7 @@ files:
|
|
152
153
|
- spec/matchers_spec.rb
|
153
154
|
- spec/rate_limiter_spec.rb
|
154
155
|
- spec/riddle_spec.rb
|
156
|
+
- spec/static_limiter_spec.rb
|
155
157
|
- spec/test_mode_spec.rb
|
156
158
|
- spec/unlimiter_spec.rb
|
157
159
|
- spec/utils_spec.rb
|
@@ -190,5 +192,6 @@ test_files:
|
|
190
192
|
- spec/utils_spec.rb
|
191
193
|
- spec/berater_spec.rb
|
192
194
|
- spec/limiter_spec.rb
|
195
|
+
- spec/static_limiter_spec.rb
|
193
196
|
- spec/inhibitor_spec.rb
|
194
197
|
- spec/unlimiter_spec.rb
|