berater 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d007dbb664fa57c921047a28dd1e5a591cf5c51fba4931dbc367ab552e4b8a8d
4
- data.tar.gz: c710d31a2d8cfdc50af4c2fae87cabad177096d13da52a7f9c48b61aa31b6038
3
+ metadata.gz: f57aeaa9319ae38bdd7d2468443fc5c9ea9ba44cecf47bc5a558f18ecd0ab274
4
+ data.tar.gz: ff14ef90856911e9e18cbfbad6f9799727c999697c5e4d96f47b5fbf665c725c
5
5
  SHA512:
6
- metadata.gz: 0d9c211bb65e7341b98e637d3f0ce0e31f0a879437c1c309fb01e1efab5e5c6b2d5044158931ab16a67cbbdebd00ddb5f10afc97430230b153d8cc236f7517ab
7
- data.tar.gz: 5d8b38bfd7683df641fd6891cd1a493079453ea6c61b2880581eeb2d963fb171365fb3448443e2548b34297d8dcfa352e67cff5688cd24d25422efb5e55d4afd
6
+ metadata.gz: c9d3abeeb9d35c608c194eaf505c5af1350a82583dd12257e70b9d4f96bd3a6cd30db802f57c3bf77d371b5deb5e856e9238eadd3990768205fd37ea3e2e3703
7
+ data.tar.gz: c98078a3c7d3a3eb4e2d523577f9d6dd7f1bbae76c9ecbd8d65961cc7aa1bab6e1a4b8fe84cfb30ee60f4d0ce11dcb58e3b94a3566cf50ee60d1f8e21bab82b1
@@ -32,7 +32,7 @@ module Berater
32
32
  @timeout = timeout
33
33
  end
34
34
 
35
- LUA_SCRIPT = <<~LUA.gsub(/^\s*(--.*\n)?/, '')
35
+ LUA_SCRIPT = <<~LUA.gsub(/^\s*|\s*--.*/, '')
36
36
  local key = KEYS[1]
37
37
  local lock_key = KEYS[2]
38
38
  local capacity = tonumber(ARGV[1])
@@ -53,20 +53,56 @@ module Berater
53
53
  end
54
54
  end
55
55
 
56
- def limit
57
- ts = Time.now.to_i
56
+ LUA_SCRIPT = <<~LUA.gsub(/^\s*|\s*--.*/, '')
57
+ local key = KEYS[1]
58
+ local ts_key = KEYS[2]
59
+ local ts = tonumber(ARGV[1])
60
+ local capacity = tonumber(ARGV[2])
61
+ local usec_per_drip = tonumber(ARGV[3])
62
+ local count = 0
63
+
64
+ -- timestamp of last update
65
+ local last_ts = tonumber(redis.call('GET', ts_key))
66
+
67
+ if last_ts then
68
+ count = tonumber(redis.call('GET', key)) or 0
69
+
70
+ -- adjust for time passing
71
+ local drips = math.floor((ts - last_ts) / usec_per_drip)
72
+ count = math.max(0, count - drips)
73
+ end
74
+
75
+ local allowed = count + 1 <= capacity
58
76
 
59
- # bucket into time slot
60
- rkey = "%s:%d" % [ cache_key(key), ts - ts % @interval_sec ]
77
+ if allowed then
78
+ count = count + 1
61
79
 
62
- count, _ = redis.multi do
63
- redis.incr rkey
64
- redis.expire rkey, @interval_sec * 2
80
+ -- time for bucket to empty, in milliseconds
81
+ local ttl = math.ceil((count * usec_per_drip) / 1000)
82
+
83
+ -- update count and last_ts, with expirations
84
+ redis.call('SET', key, count, 'PX', ttl)
85
+ redis.call('SET', ts_key, ts, 'PX', ttl)
65
86
  end
66
87
 
67
- raise Overrated if count > @count
88
+ return { count, allowed }
89
+ LUA
90
+
91
+ def limit
92
+ usec_per_drip = (@interval_sec * 10**6) / @count
93
+
94
+ # timestamp in microseconds
95
+ ts = (Time.now.to_f * 10**6).to_i
96
+
97
+ count, allowed = redis.eval(
98
+ LUA_SCRIPT,
99
+ [ cache_key(key), cache_key("#{key}-ts") ],
100
+ [ ts, @count, usec_per_drip ]
101
+ )
102
+
103
+ raise Overrated unless allowed
68
104
 
69
- lock = Lock.new(self, count, count)
105
+ lock = Lock.new(self, "#{ts}-#{count}", count)
70
106
 
71
107
  if block_given?
72
108
  begin
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -93,7 +93,7 @@ describe 'Berater.test_mode' do
93
93
  it_behaves_like 'a RateLimiter'
94
94
 
95
95
  it 'works per usual' do
96
- expect(limiter.redis).to receive(:multi).twice.and_call_original
96
+ expect(limiter.redis).to receive(:eval).twice.and_call_original
97
97
  expect(limiter.limit).to be_a Berater::Lock
98
98
  expect { limiter.limit }.to be_overloaded
99
99
  end
@@ -109,7 +109,7 @@ describe 'Berater.test_mode' do
109
109
  it_behaves_like 'a RateLimiter'
110
110
 
111
111
  it 'always works and without calling redis' do
112
- expect(limiter.redis).not_to receive(:multi)
112
+ expect(limiter.redis).not_to receive(:eval)
113
113
  expect {|block| limiter.limit(&block) }.to yield_control
114
114
  10.times { expect(limiter.limit).to be_a Berater::Lock }
115
115
  end
@@ -121,7 +121,7 @@ describe 'Berater.test_mode' do
121
121
  it_behaves_like 'a RateLimiter'
122
122
 
123
123
  it 'never works and without calling redis' do
124
- expect(limiter.redis).not_to receive(:multi)
124
+ expect(limiter.redis).not_to receive(:eval)
125
125
  expect { limiter }.to be_overloaded
126
126
  end
127
127
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: berater
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper