berater 0.3.0 → 0.4.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 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