praroter 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -1
- data/lib/praroter/filly_bucket/bucket.rb +57 -0
- data/lib/praroter/filly_bucket/bucket_state.rb +15 -0
- data/lib/praroter/{filly_bucket.rb → filly_bucket/creator.rb} +1 -65
- data/lib/praroter/{filly_bucket.lua → filly_bucket/filly_bucket.lua} +0 -0
- data/lib/praroter/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a14e168451294700f7c71bed654256c51bcf6742f186f8201f9aebe7a1a6e3e5
|
4
|
+
data.tar.gz: 5d1995a758edad1c5f054bf665f101fa85ba00bb34eb729415eaf4a7a5f13c1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb3c8fb96ff6f5fdec4fe33eff582ba3db6be91caf2714e28f6ba3b4fb79dccc7dbece841a82de1c2becf507c65124cea12257bbebf7ef856cd4fdf261cef725
|
7
|
+
data.tar.gz: 4b242a9abeccf2844cdeba9a0c939f5846255db810f7cd14bafaf05eafebbf5e70f6b021d83683ca92be3e8205b796721ee1f2017990288c8a181c71e5cddfa8
|
data/README.md
CHANGED
@@ -72,6 +72,7 @@ To capture that exception, add this to the controller:
|
|
72
72
|
|
73
73
|
```ruby
|
74
74
|
rescue_from Praroter::Throttled do |e|
|
75
|
+
response.set_header('X-Ratelimit-Cost', e.bucket_state.drained)
|
75
76
|
response.set_header('X-Ratelimit-Level', e.bucket_state.level)
|
76
77
|
response.set_header('X-Ratelimit-Capacity', e.bucket_state.capacity)
|
77
78
|
response.set_header('X-Ratelimit-Retry-After', e.retry_in_seconds)
|
@@ -140,7 +141,8 @@ def index
|
|
140
141
|
sleep(2.242)
|
141
142
|
end
|
142
143
|
|
143
|
-
rescue_from Praroter::
|
144
|
+
rescue_from Praroter::Throttled do |e|
|
145
|
+
response.set_header('X-Ratelimit-Cost', e.bucket_state.drained)
|
144
146
|
response.set_header('X-Ratelimit-Level', e.bucket_state.level)
|
145
147
|
response.set_header('X-Ratelimit-Capacity', e.bucket_state.capacity)
|
146
148
|
response.set_header('X-Ratelimit-Retry-After', e.retry_in_seconds)
|
@@ -159,6 +161,7 @@ def api_ratelimit
|
|
159
161
|
bucket_state = ratelimit_bucket.drain_block do
|
160
162
|
yield
|
161
163
|
end
|
164
|
+
response.set_header('X-Ratelimit-Cost', bucket_state.drained)
|
162
165
|
response.set_header('X-Ratelimit-Level', bucket_state.level)
|
163
166
|
response.set_header('X-Ratelimit-Capacity', bucket_state.capacity)
|
164
167
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Praroter
|
2
|
+
module FillyBucket
|
3
|
+
|
4
|
+
class Bucket
|
5
|
+
attr_reader :key, :fill_rate, :capacity
|
6
|
+
|
7
|
+
def initialize(key, fill_rate, capacity, creator)
|
8
|
+
@key = key
|
9
|
+
@fill_rate = fill_rate
|
10
|
+
@capacity = capacity
|
11
|
+
@creator = creator
|
12
|
+
end
|
13
|
+
|
14
|
+
def state
|
15
|
+
@creator.run_lua_bucket_script(self, 0)
|
16
|
+
end
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
state.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def full?
|
23
|
+
state.full?
|
24
|
+
end
|
25
|
+
|
26
|
+
def throttle!
|
27
|
+
bucket_state = state
|
28
|
+
if bucket_state.empty?
|
29
|
+
remaining_block_time = ((bucket_state.capacity - bucket_state.level).abs / bucket_state.fill_rate) + 3
|
30
|
+
raise Praroter::Throttled.new(bucket_state, remaining_block_time)
|
31
|
+
end
|
32
|
+
bucket_state
|
33
|
+
end
|
34
|
+
|
35
|
+
def drain(amount)
|
36
|
+
raise ArgumentError, "drain amount must be positive" if amount < 0
|
37
|
+
@creator.run_lua_bucket_script(self, amount)
|
38
|
+
end
|
39
|
+
|
40
|
+
def drain_block
|
41
|
+
work_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
42
|
+
yield
|
43
|
+
work_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
44
|
+
drain(((work_end - work_start) * 1000).to_i)
|
45
|
+
end
|
46
|
+
|
47
|
+
def level_key
|
48
|
+
"filly_bucket.#{key}.bucket_level"
|
49
|
+
end
|
50
|
+
|
51
|
+
def last_updated_key
|
52
|
+
"filly_bucket.#{key}.last_updated"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -1,69 +1,6 @@
|
|
1
1
|
module Praroter
|
2
|
-
|
3
2
|
module FillyBucket
|
4
3
|
|
5
|
-
class BucketState < Struct.new(:level, :capacity, :fill_rate)
|
6
|
-
def empty?
|
7
|
-
level <= 0
|
8
|
-
end
|
9
|
-
|
10
|
-
def full?
|
11
|
-
level >= capacity
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class Bucket
|
16
|
-
attr_reader :key, :fill_rate, :capacity
|
17
|
-
|
18
|
-
def initialize(key, fill_rate, capacity, creator)
|
19
|
-
@key = key
|
20
|
-
@fill_rate = fill_rate
|
21
|
-
@capacity = capacity
|
22
|
-
@creator = creator
|
23
|
-
end
|
24
|
-
|
25
|
-
def state
|
26
|
-
@creator.run_lua_bucket_script(self, 0)
|
27
|
-
end
|
28
|
-
|
29
|
-
def empty?
|
30
|
-
state.empty?
|
31
|
-
end
|
32
|
-
|
33
|
-
def full?
|
34
|
-
state.full?
|
35
|
-
end
|
36
|
-
|
37
|
-
def throttle!
|
38
|
-
bucket_state = state
|
39
|
-
if bucket_state.empty?
|
40
|
-
remaining_block_time = ((bucket_state.capacity - bucket_state.level).abs / bucket_state.fill_rate) + 3
|
41
|
-
raise Praroter::Throttled.new(bucket_state, remaining_block_time)
|
42
|
-
end
|
43
|
-
bucket_state
|
44
|
-
end
|
45
|
-
|
46
|
-
def drain(amount)
|
47
|
-
raise ArgumentError, "drain amount must be positive" if amount < 0
|
48
|
-
@creator.run_lua_bucket_script(self, amount)
|
49
|
-
end
|
50
|
-
|
51
|
-
def drain_block
|
52
|
-
work_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
53
|
-
yield
|
54
|
-
work_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
55
|
-
drain(((work_end - work_start) * 1000).to_i)
|
56
|
-
end
|
57
|
-
|
58
|
-
def level_key
|
59
|
-
"filly_bucket.#{key}.bucket_level"
|
60
|
-
end
|
61
|
-
|
62
|
-
def last_updated_key
|
63
|
-
"filly_bucket.#{key}.last_updated"
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
4
|
class Creator
|
68
5
|
LUA_SCRIPT_CODE = File.read(File.join(__dir__, "filly_bucket.lua"))
|
69
6
|
LUA_SCRIPT_HASH = Digest::SHA1.hexdigest(LUA_SCRIPT_CODE)
|
@@ -86,7 +23,7 @@ module Praroter
|
|
86
23
|
keys: [bucket.level_key, bucket.last_updated_key],
|
87
24
|
argv: [bucket.capacity, bucket.fill_rate, amount]
|
88
25
|
)
|
89
|
-
BucketState.new(new_bucket_level, bucket_capacity, fill_rate)
|
26
|
+
BucketState.new(new_bucket_level, bucket_capacity, fill_rate, amount)
|
90
27
|
rescue Redis::CommandError => e
|
91
28
|
if e.message.include? "NOSCRIPT"
|
92
29
|
# The Redis server has never seen this script before. Needs to run only once in the entire lifetime
|
@@ -103,5 +40,4 @@ module Praroter
|
|
103
40
|
end
|
104
41
|
|
105
42
|
end
|
106
|
-
|
107
43
|
end
|
File without changes
|
data/lib/praroter/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praroter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kasper Grubbe
|
@@ -176,8 +176,10 @@ files:
|
|
176
176
|
- bin/setup
|
177
177
|
- docker-compose.yml
|
178
178
|
- lib/praroter.rb
|
179
|
-
- lib/praroter/filly_bucket.
|
180
|
-
- lib/praroter/filly_bucket.rb
|
179
|
+
- lib/praroter/filly_bucket/bucket.rb
|
180
|
+
- lib/praroter/filly_bucket/bucket_state.rb
|
181
|
+
- lib/praroter/filly_bucket/creator.rb
|
182
|
+
- lib/praroter/filly_bucket/filly_bucket.lua
|
181
183
|
- lib/praroter/null_logger.rb
|
182
184
|
- lib/praroter/null_pool.rb
|
183
185
|
- lib/praroter/throttled.rb
|