berater 0.1.4 → 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 +9 -18
- data/lib/berater/base_limiter.rb +11 -17
- data/lib/berater/concurrency_limiter.rb +19 -84
- 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 +25 -53
- data/spec/concurrency_lock_spec.rb +7 -60
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dec8c5d6d428f795d1489dace166451e2a3a42592d00d71155ed5cd0d5aaa909
|
4
|
+
data.tar.gz: 6956b6c2804b6d616074e239439d4f2a8e551e900db7801226948b9eb764d0fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 327cd2f3a7430c2b81bd1988abd636bfc7d97048c762d3807555e9c1196153570dbe3045fae813852a907ec00b89b8d2c36e9269ed0562419c8d98dd6c1d2b26
|
7
|
+
data.tar.gz: ea1cb39e9201091e4a1357e888ac36f630c326a271e3cfcafa13298b8f54424f776f4dd488e046e2178905d0df9fb5c3b1f0d454e331823cafe5dd338fe9ea6c
|
data/lib/berater.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'berater/version'
|
2
|
+
require 'berater/lock'
|
2
3
|
|
3
4
|
|
4
5
|
module Berater
|
@@ -8,41 +9,26 @@ module Berater
|
|
8
9
|
|
9
10
|
MODES = {}
|
10
11
|
|
11
|
-
attr_accessor :redis
|
12
|
+
attr_accessor :redis
|
12
13
|
|
13
14
|
def configure
|
14
|
-
self.mode = :unlimited # default
|
15
|
-
|
16
15
|
yield self
|
17
16
|
end
|
18
17
|
|
19
|
-
def new(mode, *args, **opts)
|
18
|
+
def new(key, mode, *args, **opts)
|
20
19
|
klass = MODES[mode.to_sym]
|
21
20
|
|
22
21
|
unless klass
|
23
22
|
raise ArgumentError, "invalid mode: #{mode}"
|
24
23
|
end
|
25
24
|
|
26
|
-
klass.new(*args, **opts)
|
25
|
+
klass.new(key, *args, **opts)
|
27
26
|
end
|
28
27
|
|
29
28
|
def register(mode, klass)
|
30
29
|
MODES[mode.to_sym] = klass
|
31
30
|
end
|
32
31
|
|
33
|
-
def mode=(mode)
|
34
|
-
unless MODES.include? mode.to_sym
|
35
|
-
raise ArgumentError, "invalid mode: #{mode}"
|
36
|
-
end
|
37
|
-
|
38
|
-
@mode = mode.to_sym
|
39
|
-
end
|
40
|
-
|
41
|
-
def limit(*args, **opts, &block)
|
42
|
-
mode = opts.delete(:mode) { self.mode }
|
43
|
-
new(mode, *args, **opts).limit(&block)
|
44
|
-
end
|
45
|
-
|
46
32
|
def expunge
|
47
33
|
redis.scan_each(match: "#{self.name}*") do |key|
|
48
34
|
redis.del key
|
@@ -51,6 +37,11 @@ module Berater
|
|
51
37
|
|
52
38
|
end
|
53
39
|
|
40
|
+
# convenience method
|
41
|
+
def Berater(key, mode, *args, **opts, &block)
|
42
|
+
Berater.new(key, mode, *args, **opts).limit(&block)
|
43
|
+
end
|
44
|
+
|
54
45
|
# load and register limiters
|
55
46
|
require 'berater/base_limiter'
|
56
47
|
require 'berater/concurrency_limiter'
|
data/lib/berater/base_limiter.rb
CHANGED
@@ -1,31 +1,25 @@
|
|
1
1
|
module Berater
|
2
2
|
class BaseLimiter
|
3
3
|
|
4
|
-
attr_reader :options
|
5
|
-
|
6
|
-
def initialize(**opts)
|
7
|
-
@options = opts
|
8
|
-
end
|
9
|
-
|
10
|
-
def key
|
11
|
-
if options[:key]
|
12
|
-
"#{self.class}:#{options[:key]}"
|
13
|
-
else
|
14
|
-
# default value
|
15
|
-
self.class.to_s
|
16
|
-
end
|
17
|
-
end
|
4
|
+
attr_reader :key, :options
|
18
5
|
|
19
6
|
def redis
|
20
7
|
options[:redis] || Berater.redis
|
21
8
|
end
|
22
9
|
|
23
|
-
def limit
|
10
|
+
def limit
|
24
11
|
raise NotImplementedError
|
25
12
|
end
|
26
13
|
|
27
|
-
|
28
|
-
|
14
|
+
protected
|
15
|
+
|
16
|
+
def initialize(key, **opts)
|
17
|
+
@key = key
|
18
|
+
@options = opts
|
19
|
+
end
|
20
|
+
|
21
|
+
def cache_key(key)
|
22
|
+
"#{self.class}:#{key}"
|
29
23
|
end
|
30
24
|
|
31
25
|
end
|
@@ -5,14 +5,14 @@ module Berater
|
|
5
5
|
|
6
6
|
attr_reader :capacity, :timeout
|
7
7
|
|
8
|
-
def initialize(capacity, **opts)
|
9
|
-
super(**opts)
|
8
|
+
def initialize(key, capacity, **opts)
|
9
|
+
super(key, **opts)
|
10
10
|
|
11
11
|
self.capacity = capacity
|
12
12
|
self.timeout = opts[:timeout] || 0
|
13
13
|
end
|
14
14
|
|
15
|
-
def capacity=(capacity)
|
15
|
+
private def capacity=(capacity)
|
16
16
|
unless capacity.is_a? Integer
|
17
17
|
raise ArgumentError, "expected Integer, found #{capacity.class}"
|
18
18
|
end
|
@@ -22,7 +22,7 @@ module Berater
|
|
22
22
|
@capacity = capacity
|
23
23
|
end
|
24
24
|
|
25
|
-
def timeout=(timeout)
|
25
|
+
private def timeout=(timeout)
|
26
26
|
unless timeout.is_a? Integer
|
27
27
|
raise ArgumentError, "expected Integer, found #{timeout.class}"
|
28
28
|
end
|
@@ -32,107 +32,42 @@ module Berater
|
|
32
32
|
@timeout = timeout
|
33
33
|
end
|
34
34
|
|
35
|
-
class Lock
|
36
|
-
attr_reader :limiter, :id, :contention
|
37
|
-
|
38
|
-
def initialize(limiter, id, contention)
|
39
|
-
@limiter = limiter
|
40
|
-
@id = id
|
41
|
-
@contention = contention
|
42
|
-
@released = false
|
43
|
-
@locked_at = Time.now
|
44
|
-
end
|
45
|
-
|
46
|
-
def release
|
47
|
-
raise 'lock already released' if released?
|
48
|
-
raise 'lock expired' if expired?
|
49
|
-
|
50
|
-
@released = limiter.release(self)
|
51
|
-
end
|
52
|
-
|
53
|
-
def released?
|
54
|
-
@released
|
55
|
-
end
|
56
|
-
|
57
|
-
def expired?
|
58
|
-
limiter.timeout > 0 && @locked_at + limiter.timeout < Time.now
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
35
|
LUA_SCRIPT = <<~LUA.gsub(/^\s*(--.*\n)?/, '')
|
63
36
|
local key = KEYS[1]
|
37
|
+
local lock_key = KEYS[2]
|
64
38
|
local capacity = tonumber(ARGV[1])
|
65
39
|
local ts = tonumber(ARGV[2])
|
66
40
|
local ttl = tonumber(ARGV[3])
|
67
|
-
|
68
|
-
local exists
|
69
|
-
local count
|
70
41
|
local lock
|
71
42
|
|
72
|
-
--
|
73
|
-
if ttl
|
74
|
-
|
75
|
-
else
|
76
|
-
-- and refresh TTL while we're at it
|
77
|
-
exists = redis.call('EXPIRE', key, ttl * 2)
|
43
|
+
-- purge stale hosts
|
44
|
+
if ttl > 0 then
|
45
|
+
redis.call('ZREMRANGEBYSCORE', key, '-inf', ts - ttl)
|
78
46
|
end
|
79
47
|
|
80
|
-
|
81
|
-
|
82
|
-
if ttl > 0 then
|
83
|
-
redis.call('ZREMRANGEBYSCORE', key, '-inf', ts - ttl)
|
84
|
-
end
|
85
|
-
|
86
|
-
-- check capacity (subtract one for next lock entry)
|
87
|
-
count = redis.call('ZCARD', key) - 1
|
88
|
-
|
89
|
-
if count < capacity then
|
90
|
-
-- yay, grab a lock
|
91
|
-
|
92
|
-
-- regenerate next lock entry, which has score inf
|
93
|
-
lock = unpack(redis.call('ZPOPMAX', key))
|
94
|
-
redis.call('ZADD', key, 'inf', (lock + 1) % 2^52)
|
95
|
-
|
96
|
-
count = count + 1
|
97
|
-
end
|
98
|
-
elseif capacity > 0 then
|
99
|
-
count = 1
|
100
|
-
lock = "1"
|
48
|
+
-- check capacity
|
49
|
+
local count = redis.call('ZCARD', key)
|
101
50
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
if ttl > 0 then
|
106
|
-
redis.call('EXPIRE', key, ttl * 2)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
if lock then
|
111
|
-
-- store lock and timestamp
|
51
|
+
if count < capacity then
|
52
|
+
-- grab a lock
|
53
|
+
lock = redis.call('INCR', lock_key)
|
112
54
|
redis.call('ZADD', key, ts, lock)
|
55
|
+
count = count + 1
|
113
56
|
end
|
114
57
|
|
115
58
|
return { count, lock }
|
116
59
|
LUA
|
117
60
|
|
118
|
-
def limit
|
119
|
-
unless opts.empty?
|
120
|
-
return self.class.new(
|
121
|
-
capacity,
|
122
|
-
**options.merge(opts)
|
123
|
-
# **options.merge(timeout: timeout).merge(opts)
|
124
|
-
).limit(&block)
|
125
|
-
end
|
126
|
-
|
61
|
+
def limit
|
127
62
|
count, lock_id = redis.eval(
|
128
63
|
LUA_SCRIPT,
|
129
|
-
[ key ],
|
64
|
+
[ cache_key(key), cache_key('lock_id') ],
|
130
65
|
[ capacity, Time.now.to_i, timeout ]
|
131
66
|
)
|
132
67
|
|
133
68
|
raise Incapacitated unless lock_id
|
134
69
|
|
135
|
-
lock = Lock.new(self, lock_id, count)
|
70
|
+
lock = Lock.new(self, lock_id, count, -> { release(lock_id) })
|
136
71
|
|
137
72
|
if block_given?
|
138
73
|
begin
|
@@ -145,8 +80,8 @@ module Berater
|
|
145
80
|
end
|
146
81
|
end
|
147
82
|
|
148
|
-
def release(
|
149
|
-
res = redis.zrem(key,
|
83
|
+
private def release(lock_id)
|
84
|
+
res = redis.zrem(cache_key(key), lock_id)
|
150
85
|
res == true || res == 1 # depending on which version of Redis
|
151
86
|
end
|
152
87
|
|
data/lib/berater/inhibitor.rb
CHANGED
@@ -3,17 +3,11 @@ module Berater
|
|
3
3
|
|
4
4
|
class Inhibited < Overloaded; end
|
5
5
|
|
6
|
-
def initialize(*args, **opts)
|
7
|
-
super(**opts)
|
6
|
+
def initialize(key = :inhibitor, *args, **opts)
|
7
|
+
super(key, **opts)
|
8
8
|
end
|
9
9
|
|
10
|
-
def limit
|
11
|
-
unless opts.empty?
|
12
|
-
return self.class.new(
|
13
|
-
**options.merge(opts)
|
14
|
-
).limit(&block)
|
15
|
-
end
|
16
|
-
|
10
|
+
def limit
|
17
11
|
raise Inhibited
|
18
12
|
end
|
19
13
|
|
data/lib/berater/lock.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Berater
|
2
|
+
class Lock
|
3
|
+
|
4
|
+
attr_reader :limiter, :id, :contention
|
5
|
+
|
6
|
+
def initialize(limiter, id, contention, release_fn = nil)
|
7
|
+
@limiter = limiter
|
8
|
+
@id = id
|
9
|
+
@contention = contention
|
10
|
+
@locked_at = Time.now
|
11
|
+
@release_fn = release_fn
|
12
|
+
@released_at = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def locked?
|
16
|
+
@released_at.nil? && !expired?
|
17
|
+
end
|
18
|
+
|
19
|
+
def expired?
|
20
|
+
timeout > 0 && @locked_at + timeout < Time.now
|
21
|
+
end
|
22
|
+
|
23
|
+
def release
|
24
|
+
raise 'lock expired' if expired?
|
25
|
+
raise 'lock already released' unless locked?
|
26
|
+
|
27
|
+
@released_at = Time.now
|
28
|
+
@release_fn ? @release_fn.call : true
|
29
|
+
end
|
30
|
+
|
31
|
+
private def timeout
|
32
|
+
limiter.respond_to?(:timeout) ? limiter.timeout : 0
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/lib/berater/rate_limiter.rb
CHANGED
@@ -5,14 +5,14 @@ module Berater
|
|
5
5
|
|
6
6
|
attr_accessor :count, :interval
|
7
7
|
|
8
|
-
def initialize(count, interval, **opts)
|
9
|
-
super(**opts)
|
8
|
+
def initialize(key, count, interval, **opts)
|
9
|
+
super(key, **opts)
|
10
10
|
|
11
11
|
self.count = count
|
12
12
|
self.interval = interval
|
13
13
|
end
|
14
14
|
|
15
|
-
def count=(count)
|
15
|
+
private def count=(count)
|
16
16
|
unless count.is_a? Integer
|
17
17
|
raise ArgumentError, "expected Integer, found #{count.class}"
|
18
18
|
end
|
@@ -22,7 +22,7 @@ module Berater
|
|
22
22
|
@count = count
|
23
23
|
end
|
24
24
|
|
25
|
-
def interval=(interval)
|
25
|
+
private def interval=(interval)
|
26
26
|
@interval = interval.dup
|
27
27
|
|
28
28
|
case @interval
|
@@ -51,19 +51,11 @@ module Berater
|
|
51
51
|
@interval
|
52
52
|
end
|
53
53
|
|
54
|
-
def limit
|
55
|
-
unless opts.empty?
|
56
|
-
return self.class.new(
|
57
|
-
count,
|
58
|
-
interval,
|
59
|
-
options.merge(opts)
|
60
|
-
).limit(&block)
|
61
|
-
end
|
62
|
-
|
54
|
+
def limit
|
63
55
|
ts = Time.now.to_i
|
64
56
|
|
65
57
|
# bucket into time slot
|
66
|
-
rkey = "%s:%d" % [ key, ts - ts % @interval ]
|
58
|
+
rkey = "%s:%d" % [ cache_key(key), ts - ts % @interval ]
|
67
59
|
|
68
60
|
count, _ = redis.multi do
|
69
61
|
redis.incr rkey
|
@@ -72,10 +64,16 @@ module Berater
|
|
72
64
|
|
73
65
|
raise Overrated if count > @count
|
74
66
|
|
67
|
+
lock = Lock.new(self, count, count)
|
68
|
+
|
75
69
|
if block_given?
|
76
|
-
|
70
|
+
begin
|
71
|
+
yield lock
|
72
|
+
ensure
|
73
|
+
lock.release
|
74
|
+
end
|
77
75
|
else
|
78
|
-
|
76
|
+
lock
|
79
77
|
end
|
80
78
|
end
|
81
79
|
|
data/lib/berater/unlimiter.rb
CHANGED
@@ -1,18 +1,23 @@
|
|
1
1
|
module Berater
|
2
2
|
class Unlimiter < BaseLimiter
|
3
3
|
|
4
|
-
def initialize(*args, **opts)
|
5
|
-
super(**opts)
|
4
|
+
def initialize(key = :unlimiter, *args, **opts)
|
5
|
+
super(key, **opts)
|
6
6
|
end
|
7
7
|
|
8
|
-
def limit
|
9
|
-
|
10
|
-
|
11
|
-
**options.merge(opts)
|
12
|
-
).limit(&block)
|
13
|
-
end
|
8
|
+
def limit
|
9
|
+
count = redis.incr(cache_key('count'))
|
10
|
+
lock = Lock.new(self, count, count)
|
14
11
|
|
15
|
-
|
12
|
+
if block_given?
|
13
|
+
begin
|
14
|
+
yield lock
|
15
|
+
ensure
|
16
|
+
lock.release
|
17
|
+
end
|
18
|
+
else
|
19
|
+
lock
|
20
|
+
end
|
16
21
|
end
|
17
22
|
|
18
23
|
end
|
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,7 +61,7 @@ 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
|
@@ -81,26 +79,26 @@ describe Berater::ConcurrencyLimiter do
|
|
81
79
|
end
|
82
80
|
|
83
81
|
it 'limits excessive calls' do
|
84
|
-
expect(limiter.limit).to be_a Berater::
|
85
|
-
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
|
86
84
|
|
87
85
|
expect(limiter).to be_incapacitated
|
88
86
|
end
|
89
87
|
|
90
88
|
it 'times out locks' do
|
91
|
-
expect(limiter.limit).to be_a Berater::
|
92
|
-
expect(limiter.limit).to be_a Berater::
|
89
|
+
expect(limiter.limit).to be_a Berater::Lock
|
90
|
+
expect(limiter.limit).to be_a Berater::Lock
|
93
91
|
expect(limiter).to be_incapacitated
|
94
92
|
|
95
93
|
Timecop.travel(1)
|
96
94
|
|
97
|
-
expect(limiter.limit).to be_a Berater::
|
98
|
-
expect(limiter.limit).to be_a Berater::
|
95
|
+
expect(limiter.limit).to be_a Berater::Lock
|
96
|
+
expect(limiter.limit).to be_a Berater::Lock
|
99
97
|
expect(limiter).to be_incapacitated
|
100
98
|
end
|
101
99
|
|
102
100
|
context 'with capacity 0' do
|
103
|
-
let(:limiter) { described_class.new(0) }
|
101
|
+
let(:limiter) { described_class.new(:key, 0) }
|
104
102
|
|
105
103
|
it 'always fails' do
|
106
104
|
expect(limiter).to be_incapacitated
|
@@ -109,60 +107,34 @@ describe Berater::ConcurrencyLimiter do
|
|
109
107
|
end
|
110
108
|
|
111
109
|
context 'with same key, different limiters' do
|
112
|
-
let(:limiter_one) { described_class.new(1) }
|
113
|
-
let(:limiter_two) { described_class.new(1) }
|
110
|
+
let(:limiter_one) { described_class.new(:key, 1) }
|
111
|
+
let(:limiter_two) { described_class.new(:key, 1) }
|
114
112
|
|
115
113
|
it { expect(limiter_one.key).to eq limiter_two.key }
|
116
114
|
|
117
115
|
it 'works as expected' do
|
118
|
-
expect(limiter_one.limit).to be_a Berater::
|
116
|
+
expect(limiter_one.limit).to be_a Berater::Lock
|
119
117
|
|
120
118
|
expect(limiter_one).to be_incapacitated
|
121
119
|
expect(limiter_two).to be_incapacitated
|
122
120
|
end
|
123
121
|
end
|
124
122
|
|
125
|
-
context 'with different keys, same limiter' do
|
126
|
-
let(:limiter) { described_class.new(1) }
|
127
|
-
|
128
|
-
it 'works as expected' do
|
129
|
-
one_lock = limiter.limit(key: :one)
|
130
|
-
expect(one_lock).to be_a Berater::ConcurrencyLimiter::Lock
|
131
|
-
|
132
|
-
expect { limiter.limit(key: :one) {} }.to be_incapacitated
|
133
|
-
expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
|
134
|
-
|
135
|
-
two_lock = limiter.limit(key: :two)
|
136
|
-
expect(two_lock).to be_a Berater::ConcurrencyLimiter::Lock
|
137
|
-
|
138
|
-
expect { limiter.limit(key: :one) {} }.to be_incapacitated
|
139
|
-
expect { limiter.limit(key: :two) {} }.to be_incapacitated
|
140
|
-
|
141
|
-
one_lock.release
|
142
|
-
expect { limiter.limit(key: :one) {} }.not_to be_incapacitated
|
143
|
-
expect { limiter.limit(key: :two) {} }.to be_incapacitated
|
144
|
-
|
145
|
-
two_lock.release
|
146
|
-
expect { limiter.limit(key: :one) {} }.not_to be_incapacitated
|
147
|
-
expect { limiter.limit(key: :two) {} }.not_to be_incapacitated
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
123
|
context 'with same key, different capacities' do
|
152
|
-
let(:limiter_one) { described_class.new(1) }
|
153
|
-
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) }
|
154
126
|
|
155
127
|
it { expect(limiter_one.capacity).not_to eq limiter_two.capacity }
|
156
128
|
|
157
129
|
it 'works as expected' do
|
158
130
|
one_lock = limiter_one.limit
|
159
|
-
expect(one_lock).to be_a Berater::
|
131
|
+
expect(one_lock).to be_a Berater::Lock
|
160
132
|
|
161
133
|
expect(limiter_one).to be_incapacitated
|
162
134
|
expect(limiter_two).not_to be_incapacitated
|
163
135
|
|
164
136
|
two_lock = limiter_two.limit
|
165
|
-
expect(two_lock).to be_a Berater::
|
137
|
+
expect(two_lock).to be_a Berater::Lock
|
166
138
|
|
167
139
|
expect(limiter_one).to be_incapacitated
|
168
140
|
expect(limiter_two).to be_incapacitated
|
@@ -178,21 +150,21 @@ describe Berater::ConcurrencyLimiter do
|
|
178
150
|
end
|
179
151
|
|
180
152
|
context 'with different keys, different limiters' do
|
181
|
-
let(:limiter_one) { described_class.new(
|
182
|
-
let(:limiter_two) { described_class.new(
|
153
|
+
let(:limiter_one) { described_class.new(:one, 1) }
|
154
|
+
let(:limiter_two) { described_class.new(:two, 1) }
|
183
155
|
|
184
156
|
it 'works as expected' do
|
185
157
|
expect(limiter_one).not_to be_incapacitated
|
186
158
|
expect(limiter_two).not_to be_incapacitated
|
187
159
|
|
188
160
|
one_lock = limiter_one.limit
|
189
|
-
expect(one_lock).to be_a Berater::
|
161
|
+
expect(one_lock).to be_a Berater::Lock
|
190
162
|
|
191
163
|
expect(limiter_one).to be_incapacitated
|
192
164
|
expect(limiter_two).not_to be_incapacitated
|
193
165
|
|
194
166
|
two_lock = limiter_two.limit
|
195
|
-
expect(two_lock).to be_a Berater::
|
167
|
+
expect(two_lock).to be_a Berater::Lock
|
196
168
|
|
197
169
|
expect(limiter_one).to be_incapacitated
|
198
170
|
expect(limiter_two).to be_incapacitated
|
@@ -1,62 +1,7 @@
|
|
1
|
-
describe Berater::
|
2
|
-
|
1
|
+
describe Berater::Lock do
|
2
|
+
it_behaves_like 'a lock', Berater.new(:key, :concurrency, 3)
|
3
3
|
|
4
|
-
let(:limiter) { Berater.new(:concurrency, 3) }
|
5
|
-
|
6
|
-
describe '#contention' do
|
7
|
-
it 'tracks contention' do
|
8
|
-
lock_1 = limiter.limit
|
9
|
-
expect(lock_1.contention).to eq 1
|
10
|
-
|
11
|
-
lock_2 = limiter.limit
|
12
|
-
expect(lock_2.contention).to eq 2
|
13
|
-
|
14
|
-
limiter.limit do |lock_3|
|
15
|
-
expect(lock_3.contention).to eq 3
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'works in block mode' do
|
20
|
-
lock_1 = limiter.limit
|
21
|
-
|
22
|
-
limiter.limit do |lock_2|
|
23
|
-
expect(lock_1.contention).to eq 1
|
24
|
-
expect(lock_2.contention).to eq 2
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
describe '#release' do
|
30
|
-
it 'can not be released twice' do
|
31
|
-
lock = limiter.limit
|
32
|
-
expect(lock.release).to be true
|
33
|
-
expect { lock.release }.to raise_error(RuntimeError, /already/)
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'does not work in block mode' do
|
37
|
-
expect do
|
38
|
-
limiter.limit do |lock|
|
39
|
-
lock.release
|
40
|
-
end
|
41
|
-
end.to raise_error(RuntimeError, /already/)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
describe '#released?' do
|
46
|
-
it 'works' do
|
47
|
-
lock = limiter.limit
|
48
|
-
expect(lock.released?).to be false
|
49
|
-
|
50
|
-
lock.release
|
51
|
-
expect(lock.released?).to be true
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'works in block mode' do
|
55
|
-
limiter.limit do |lock|
|
56
|
-
expect(lock.released?).to be false
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
4
|
+
let(:limiter) { Berater.new(:key, :concurrency, 3) }
|
60
5
|
|
61
6
|
describe '#expired?' do
|
62
7
|
let!(:lock) { limiter.limit }
|
@@ -65,10 +10,12 @@ describe Berater::ConcurrencyLimiter::Lock do
|
|
65
10
|
it { expect(limiter.timeout).to eq 0 }
|
66
11
|
|
67
12
|
it 'never expires' do
|
13
|
+
expect(lock.locked?).to be true
|
68
14
|
expect(lock.expired?).to be false
|
69
15
|
|
70
16
|
Timecop.travel(1_000)
|
71
17
|
|
18
|
+
expect(lock.locked?).to be true
|
72
19
|
expect(lock.expired?).to be false
|
73
20
|
end
|
74
21
|
end
|
@@ -76,14 +23,14 @@ describe Berater::ConcurrencyLimiter::Lock do
|
|
76
23
|
context 'when timeout is set and exceeded' do
|
77
24
|
before { Timecop.travel(1) }
|
78
25
|
|
79
|
-
let(:limiter) { Berater.new(:concurrency, 3, timeout: 1) }
|
26
|
+
let(:limiter) { Berater.new(:key, :concurrency, 3, timeout: 1) }
|
80
27
|
|
81
28
|
it 'expires' do
|
82
29
|
expect(lock.expired?).to be true
|
30
|
+
expect(lock.locked?).to be false
|
83
31
|
end
|
84
32
|
|
85
33
|
it 'fails to release' do
|
86
|
-
expect(lock.released?).to be false
|
87
34
|
expect { lock.release }.to raise_error(RuntimeError, /expired/)
|
88
35
|
end
|
89
36
|
end
|
data/spec/inhibitor_spec.rb
CHANGED
@@ -1,36 +1,24 @@
|
|
1
1
|
describe Berater::Inhibitor do
|
2
|
-
before { Berater.mode = :inhibited }
|
3
|
-
|
4
2
|
describe '.new' do
|
5
3
|
it 'initializes without any arguments or options' do
|
6
4
|
expect(described_class.new).to be_a described_class
|
7
5
|
end
|
8
6
|
|
9
7
|
it 'initializes with any arguments and options' do
|
10
|
-
expect(described_class.new(:abc, x: 123)).to be_a described_class
|
8
|
+
expect(described_class.new(:abc, :def, x: 123)).to be_a described_class
|
11
9
|
end
|
12
10
|
|
13
11
|
it 'has default values' do
|
14
|
-
expect(described_class.new.key).to
|
12
|
+
expect(described_class.new.key).to be :inhibitor
|
15
13
|
expect(described_class.new.redis).to be Berater.redis
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
|
-
describe '.limit' do
|
20
|
-
it 'always limits' do
|
21
|
-
expect { described_class.limit }.to be_inhibited
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'works with any arguments or options' do
|
25
|
-
expect { described_class.limit(:abc, x: 123) }.to be_inhibited
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
17
|
describe '#limit' do
|
30
18
|
let(:limiter) { described_class.new }
|
31
19
|
|
32
20
|
it 'always limits' do
|
33
|
-
expect {
|
21
|
+
expect { limiter.limit }.to be_inhibited
|
34
22
|
end
|
35
23
|
end
|
36
24
|
|
data/spec/matcher_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
describe 'be_overloaded' do
|
2
2
|
context 'Berater::Unlimiter' do
|
3
|
-
let(:limiter) { Berater.new(:unlimited) }
|
3
|
+
let(:limiter) { Berater.new(:key, :unlimited) }
|
4
4
|
|
5
5
|
it { expect(limiter).not_to be_overloaded }
|
6
6
|
it { expect(limiter).not_to be_inhibited }
|
@@ -19,7 +19,7 @@ describe 'be_overloaded' do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
context 'Berater::Inhibitor' do
|
22
|
-
let(:limiter) { Berater.new(:inhibited) }
|
22
|
+
let(:limiter) { Berater.new(:key, :inhibited) }
|
23
23
|
|
24
24
|
it { expect(limiter).to be_overloaded }
|
25
25
|
it { expect(limiter).to be_inhibited }
|
@@ -32,7 +32,7 @@ describe 'be_overloaded' do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'Berater::RateLimiter' do
|
35
|
-
let(:limiter) { Berater.new(:rate, 1, :second) }
|
35
|
+
let(:limiter) { Berater.new(:key, :rate, 1, :second) }
|
36
36
|
|
37
37
|
it { expect(limiter).not_to be_overloaded }
|
38
38
|
it { expect(limiter).not_to be_inhibited }
|
@@ -67,7 +67,7 @@ describe 'be_overloaded' do
|
|
67
67
|
end
|
68
68
|
|
69
69
|
context 'Berater::ConcurrencyLimiter' do
|
70
|
-
let(:limiter) { Berater.new(:concurrency, 1) }
|
70
|
+
let(:limiter) { Berater.new(:key, :concurrency, 1) }
|
71
71
|
|
72
72
|
it { expect(limiter).not_to be_overloaded }
|
73
73
|
it { expect(limiter).not_to be_inhibited }
|
data/spec/rate_limiter_spec.rb
CHANGED
@@ -1,23 +1,22 @@
|
|
1
1
|
describe Berater::RateLimiter do
|
2
|
-
before { Berater.mode = :rate }
|
3
2
|
|
4
3
|
describe '.new' do
|
5
|
-
let(:limiter) { described_class.new(1, :second) }
|
4
|
+
let(:limiter) { described_class.new(:key, 1, :second) }
|
6
5
|
|
7
6
|
it 'initializes' do
|
7
|
+
expect(limiter.key).to be :key
|
8
8
|
expect(limiter.count).to eq 1
|
9
9
|
expect(limiter.interval).to eq 1
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'has default values' do
|
13
|
-
expect(limiter.key).to eq described_class.to_s
|
14
13
|
expect(limiter.redis).to be Berater.redis
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
18
17
|
describe '#count' do
|
19
18
|
def expect_count(count)
|
20
|
-
limiter = described_class.new(count, :second)
|
19
|
+
limiter = described_class.new(:key, count, :second)
|
21
20
|
expect(limiter.count).to eq count
|
22
21
|
end
|
23
22
|
|
@@ -28,7 +27,7 @@ describe Berater::RateLimiter do
|
|
28
27
|
context 'with erroneous values' do
|
29
28
|
def expect_bad_count(count)
|
30
29
|
expect do
|
31
|
-
described_class.new(count, :second)
|
30
|
+
described_class.new(:key, count, :second)
|
32
31
|
end.to raise_error ArgumentError
|
33
32
|
end
|
34
33
|
|
@@ -41,7 +40,7 @@ describe Berater::RateLimiter do
|
|
41
40
|
|
42
41
|
describe '#interval' do
|
43
42
|
def expect_interval(interval, expected)
|
44
|
-
limiter = described_class.new(1, interval)
|
43
|
+
limiter = described_class.new(:key, 1, interval)
|
45
44
|
expect(limiter.interval).to eq expected
|
46
45
|
end
|
47
46
|
|
@@ -73,7 +72,7 @@ describe Berater::RateLimiter do
|
|
73
72
|
context 'with erroneous values' do
|
74
73
|
def expect_bad_interval(interval)
|
75
74
|
expect do
|
76
|
-
described_class.new(1, interval)
|
75
|
+
described_class.new(:key, 1, interval)
|
77
76
|
end.to raise_error(ArgumentError)
|
78
77
|
end
|
79
78
|
|
@@ -84,82 +83,60 @@ describe Berater::RateLimiter do
|
|
84
83
|
end
|
85
84
|
|
86
85
|
describe '#limit' do
|
87
|
-
let(:limiter) { described_class.new(3, :second) }
|
86
|
+
let(:limiter) { described_class.new(:key, 3, :second) }
|
88
87
|
|
89
88
|
it 'works' do
|
90
|
-
expect(limiter.limit).to eq 1
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'counts' do
|
94
|
-
expect(limiter.limit).to eq 1
|
95
|
-
expect(limiter.limit).to eq 2
|
96
|
-
expect(limiter.limit).to eq 3
|
97
|
-
end
|
98
|
-
|
99
|
-
it 'yields' do
|
100
89
|
expect {|b| limiter.limit(&b) }.to yield_control
|
101
90
|
expect(limiter.limit { 123 }).to eq 123
|
102
91
|
end
|
103
92
|
|
93
|
+
it 'works without a block' do
|
94
|
+
expect(limiter.limit).to be_a Berater::Lock
|
95
|
+
end
|
96
|
+
|
104
97
|
it 'limits excessive calls' do
|
105
98
|
3.times { limiter.limit }
|
106
99
|
|
107
|
-
expect
|
100
|
+
expect(limiter).to be_overrated
|
108
101
|
end
|
109
102
|
|
110
103
|
it 'limit resets over time' do
|
111
|
-
|
112
|
-
expect(limiter.limit).to eq 2
|
113
|
-
expect(limiter.limit).to eq 3
|
104
|
+
3.times { limiter.limit }
|
114
105
|
expect(limiter).to be_overrated
|
115
106
|
|
116
107
|
# travel forward a second
|
117
108
|
Timecop.freeze(1)
|
118
109
|
|
119
|
-
|
120
|
-
expect(limiter.limit).to eq 2
|
121
|
-
expect(limiter.limit).to eq 3
|
110
|
+
3.times { limiter.limit }
|
122
111
|
expect(limiter).to be_overrated
|
123
112
|
end
|
124
113
|
end
|
125
114
|
|
126
115
|
context 'with same key, different limiters' do
|
127
|
-
let(:limiter_one) { described_class.new(1, :second) }
|
128
|
-
let(:limiter_two) { described_class.new(1, :second) }
|
129
|
-
|
130
|
-
it 'works as expected' do
|
131
|
-
expect(limiter_one.limit).to eq 1
|
132
|
-
|
133
|
-
expect { limiter_one }.to be_overrated
|
134
|
-
expect { limiter_two }.to be_overrated
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
context 'with different keys, same limiter' do
|
139
|
-
let(:limiter) { described_class.new(1, :second) }
|
116
|
+
let(:limiter_one) { described_class.new(:key, 1, :second) }
|
117
|
+
let(:limiter_two) { described_class.new(:key, 1, :second) }
|
140
118
|
|
141
119
|
it 'works as expected' do
|
142
|
-
expect
|
143
|
-
expect { limiter.limit(key: :one) }.to be_overrated
|
120
|
+
expect(limiter_one.limit).not_to be_overrated
|
144
121
|
|
145
|
-
expect
|
146
|
-
expect
|
122
|
+
expect(limiter_one).to be_overrated
|
123
|
+
expect(limiter_two).to be_overrated
|
147
124
|
end
|
148
125
|
end
|
149
126
|
|
150
127
|
context 'with different keys, different limiters' do
|
151
|
-
let(:limiter_one) { described_class.new(1, :second
|
152
|
-
let(:limiter_two) { described_class.new(2, :second
|
128
|
+
let(:limiter_one) { described_class.new(:one, 1, :second) }
|
129
|
+
let(:limiter_two) { described_class.new(:two, 2, :second) }
|
153
130
|
|
154
131
|
it 'works as expected' do
|
155
|
-
expect(limiter_one.limit).
|
156
|
-
expect(limiter_two.limit).
|
132
|
+
expect(limiter_one.limit).not_to be_overrated
|
133
|
+
expect(limiter_two.limit).not_to be_overrated
|
157
134
|
|
158
|
-
expect
|
159
|
-
expect(limiter_two.limit).
|
135
|
+
expect(limiter_one).to be_overrated
|
136
|
+
expect(limiter_two.limit).not_to be_overrated
|
160
137
|
|
161
|
-
expect
|
162
|
-
expect
|
138
|
+
expect(limiter_one).to be_overrated
|
139
|
+
expect(limiter_two).to be_overrated
|
163
140
|
end
|
164
141
|
end
|
165
142
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
describe Berater::Lock do
|
2
|
+
it_behaves_like 'a lock', Berater.new(:key, :rate, 3, :second)
|
3
|
+
|
4
|
+
let(:limiter) { Berater.new(:key, :rate, 3, :second) }
|
5
|
+
|
6
|
+
describe '#expired?' do
|
7
|
+
let!(:lock) { limiter.limit }
|
8
|
+
|
9
|
+
it 'never expires' do
|
10
|
+
expect(lock.locked?).to be true
|
11
|
+
expect(lock.expired?).to be false
|
12
|
+
|
13
|
+
Timecop.travel(1_000)
|
14
|
+
|
15
|
+
expect(lock.locked?).to be true
|
16
|
+
expect(lock.expired?).to be false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/spec/unlimiter_spec.rb
CHANGED
@@ -1,50 +1,28 @@
|
|
1
1
|
describe Berater::Unlimiter do
|
2
|
-
before { Berater.mode = :unlimited }
|
3
|
-
|
4
2
|
describe '.new' do
|
5
3
|
it 'initializes without any arguments or options' do
|
6
4
|
expect(described_class.new).to be_a described_class
|
7
5
|
end
|
8
6
|
|
9
7
|
it 'initializes with any arguments and options' do
|
10
|
-
expect(described_class.new(:abc, x: 123)).to be_a described_class
|
8
|
+
expect(described_class.new(:abc, :def, x: 123)).to be_a described_class
|
11
9
|
end
|
12
10
|
|
13
11
|
it 'has default values' do
|
14
|
-
expect(described_class.new.key).to
|
12
|
+
expect(described_class.new.key).to be :unlimiter
|
15
13
|
expect(described_class.new.redis).to be Berater.redis
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
|
-
describe '.limit' do
|
20
|
-
it 'works' do
|
21
|
-
expect(described_class.limit).to be_nil
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'yields' do
|
25
|
-
expect {|b| described_class.limit(&b) }.to yield_control
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'is never overloaded' do
|
29
|
-
10.times do
|
30
|
-
expect { described_class.limit }.not_to be_overloaded
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'works with any arguments or options' do
|
35
|
-
expect(described_class.limit(:abc, x: 123)).to be_nil
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
17
|
describe '#limit' do
|
40
18
|
let(:limiter) { described_class.new }
|
41
19
|
|
42
20
|
it 'works' do
|
43
|
-
expect
|
21
|
+
expect {|b| limiter.limit(&b) }.to yield_control
|
44
22
|
end
|
45
23
|
|
46
|
-
it '
|
47
|
-
expect
|
24
|
+
it 'works without a block' do
|
25
|
+
expect(limiter.limit).to be_a Berater::Lock
|
48
26
|
end
|
49
27
|
|
50
28
|
it 'is never overloaded' do
|
@@ -52,10 +30,8 @@ describe Berater::Unlimiter do
|
|
52
30
|
expect { limiter.limit }.not_to be_overloaded
|
53
31
|
end
|
54
32
|
end
|
55
|
-
|
56
|
-
it 'works with any arguments or options' do
|
57
|
-
expect(limiter.limit(x: 123)).to be_nil
|
58
|
-
end
|
59
33
|
end
|
60
34
|
|
35
|
+
it_behaves_like 'a lock', described_class.new
|
36
|
+
|
61
37
|
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.2.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-02-
|
11
|
+
date: 2021-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- lib/berater/base_limiter.rb
|
119
119
|
- lib/berater/concurrency_limiter.rb
|
120
120
|
- lib/berater/inhibitor.rb
|
121
|
+
- lib/berater/lock.rb
|
121
122
|
- lib/berater/rate_limiter.rb
|
122
123
|
- lib/berater/unlimiter.rb
|
123
124
|
- lib/berater/version.rb
|
@@ -127,6 +128,7 @@ files:
|
|
127
128
|
- spec/inhibitor_spec.rb
|
128
129
|
- spec/matcher_spec.rb
|
129
130
|
- spec/rate_limiter_spec.rb
|
131
|
+
- spec/rate_lock_spec.rb
|
130
132
|
- spec/unlimiter_spec.rb
|
131
133
|
homepage: https://github.com/dpep/berater_rb
|
132
134
|
licenses:
|
@@ -154,6 +156,7 @@ summary: Berater
|
|
154
156
|
test_files:
|
155
157
|
- spec/rate_limiter_spec.rb
|
156
158
|
- spec/matcher_spec.rb
|
159
|
+
- spec/rate_lock_spec.rb
|
157
160
|
- spec/concurrency_limiter_spec.rb
|
158
161
|
- spec/concurrency_lock_spec.rb
|
159
162
|
- spec/berater_spec.rb
|