simple_throttle 1.0.7 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -2
- data/README.md +15 -0
- data/VERSION +1 -1
- data/lib/simple_throttle.rb +41 -18
- data/simple_throttle.gemspec +2 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6777d3382abb6c7e07c503e56676a8fe62be43611b2c623ac18f89928708572e
|
4
|
+
data.tar.gz: da8f2432ec4bc9dcf68e42faaf6213d662379c3ded9dfb16c1eb8c2cd27cb62c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f90008dc9debdfb8e206d25f112c0e312c4c27060256464eba6b175aefb9b5be063bbcdfb1188bbcaab05caedb6f1197d28d7b4a5ee3b40819692e4d57e9642a
|
7
|
+
data.tar.gz: 31326e2b2ab31f4fbccf8dbd6fb7d0455acdd1e745da7d90ea77f526f320fef4d77c94cc98dffc03ce9d76f4c3fc7ed167aa5b9bd72761dfc0463296b7424459
|
data/CHANGELOG.md
CHANGED
@@ -4,39 +4,58 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## 1.1.1
|
8
|
+
|
9
|
+
### Fixed
|
10
|
+
|
11
|
+
- Fixed `increment!` method to return the correct value after removing expired requests rather than the raw count from Redis.
|
12
|
+
|
13
|
+
## 1.1.0
|
14
|
+
|
15
|
+
### Added
|
16
|
+
|
17
|
+
- Added `increment!` method to increment the throttle by a given amount and return the current count.
|
18
|
+
|
7
19
|
## 1.0.7
|
8
20
|
|
9
21
|
## Fixed
|
22
|
+
|
10
23
|
- Fixed peek method to return the correct value rather than the raw value from Redis.
|
11
24
|
|
12
25
|
### Changed
|
26
|
+
|
13
27
|
- Updated repository location and gem owners
|
14
28
|
|
15
29
|
## 1.0.6
|
16
30
|
|
17
31
|
### Added
|
32
|
+
|
18
33
|
- Add support for `pause_to_recover` flag on throttles to force calls to the throttle to fail until the process calling them has paused temporarily.
|
19
34
|
|
20
35
|
## 1.0.5
|
21
36
|
|
22
37
|
### Added
|
38
|
+
|
23
39
|
- Make loading lua script play better with Redis clusters.
|
24
40
|
- Handle failures loading lua script on Redis server to prevent infinite loop.
|
25
41
|
|
26
42
|
## 1.0.4
|
27
43
|
|
28
44
|
### Fixed
|
29
|
-
|
45
|
+
|
46
|
+
- Fix `wait_time` method to match the documentation from [bc-swoop](https://github.com/bc-swoop)
|
30
47
|
|
31
48
|
## 1.0.3
|
32
49
|
|
33
50
|
### Changed
|
51
|
+
|
34
52
|
- Ensure that arguments sent to Redis Lua script are cast to integers.
|
35
53
|
|
36
54
|
## 1.0.2
|
37
55
|
|
38
56
|
### Added
|
39
|
-
|
57
|
+
|
58
|
+
- Throttle instances can now specify the Redis instance to override the global setting.
|
40
59
|
- Redis instance now defaults to the default redis instance: `Redis.new`.
|
41
60
|
- Optimize loading LUA script to Redis; now done globally instead of per throttle instance.
|
42
61
|
|
@@ -44,10 +63,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
44
63
|
## 1.0.1
|
45
64
|
|
46
65
|
### Added
|
66
|
+
|
47
67
|
- Added mutex in `SimpleThrottle.add` to ensure thread safety when adding global throttles.
|
48
68
|
|
49
69
|
|
50
70
|
## 1.0.0
|
51
71
|
|
52
72
|
### Added
|
73
|
+
|
53
74
|
- Simple Redis backed throttle for Ruby.
|
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# Simple Throttle
|
2
|
+
|
3
|
+
[![Continuous Integration](https://github.com/bdurand/simple_throttle/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/simple_throttle/actions/workflows/continuous_integration.yml)
|
4
|
+
[![Regression Test](https://github.com/bdurand/simple_throttle/actions/workflows/regression_test.yml/badge.svg)](https://github.com/bdurand/simple_throttle/actions/workflows/regression_test.yml)
|
1
5
|
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
2
6
|
|
3
7
|
This gem provides a very simple throttling mechanism backed by Redis for limiting access to a resource. The throttle can be thought of as a limit on the number of calls in a set time frame (i.e. 100 calls per hour).
|
@@ -37,6 +41,17 @@ Calling `allowed!` will return `true` if the throttle limit has not yet been rea
|
|
37
41
|
|
38
42
|
The throttle data is kept in redis as a list of timestamps and will be auto expired if it falls out of use. The thottles time windows are rolling time windows and more calls will be allowed as soon as possible. So, if you have a throttle of, 100 requests per hour, and the throttle kicks in, you will be able to make the next throttled call one hour after the first call being tracked, not one hour after the last call.
|
39
43
|
|
44
|
+
You can also increment the throttle yourself with the `increment!` method. This will increment the throttle by the given amount and return the current count. The count will be capped by the throttle limit since excess requests beyond the limit are not tracked in Redis for performance reasons.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
count = throttle.increment!
|
48
|
+
if count <= throttle.limit
|
49
|
+
do_something
|
50
|
+
else
|
51
|
+
raise "Too many requests: #{count}"
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
40
55
|
### Pause to recover option
|
41
56
|
|
42
57
|
Throttles can also specify a `pause_to_recover` option set when they are created. When this flag is set, once a throttle check fails, it will continue to fail until the rate at which it is called drops below the maximum rate allowed by the throttle. This is designed for use where you want to detect run away processes constantly hitting a service. Without this set, the process would be able to utilize the resource up to the set limit. With it set, the process would need to pause temporarily to succeed again.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.1
|
data/lib/simple_throttle.rb
CHANGED
@@ -15,11 +15,12 @@ class SimpleThrottle
|
|
15
15
|
local limit = tonumber(ARGV[1])
|
16
16
|
local ttl = tonumber(ARGV[2])
|
17
17
|
local now = ARGV[3]
|
18
|
-
local
|
19
|
-
local
|
18
|
+
local pause_to_recover = tonumber(ARGV[4])
|
19
|
+
local amount = tonumber(ARGV[5])
|
20
|
+
local cleanup = tonumber(ARGV[6])
|
20
21
|
|
21
22
|
local size = redis.call('llen', list_key)
|
22
|
-
if size >= limit then
|
23
|
+
if size >= limit or (cleanup > 0 and size > 0) then
|
23
24
|
local expired = tonumber(now) - ttl
|
24
25
|
while size > 0 do
|
25
26
|
local t = redis.call('lpop', list_key)
|
@@ -31,12 +32,22 @@ class SimpleThrottle
|
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
if
|
35
|
-
|
35
|
+
if pause_to_recover > 0 then
|
36
|
+
limit = limit + 1
|
37
|
+
end
|
38
|
+
|
39
|
+
if size + amount > limit then
|
40
|
+
amount = (limit - size) + 1
|
41
|
+
end
|
42
|
+
|
43
|
+
if size < limit then
|
44
|
+
for i = 1, amount do
|
45
|
+
redis.call('rpush', list_key, now)
|
46
|
+
end
|
36
47
|
redis.call('pexpire', list_key, ttl)
|
37
48
|
end
|
38
49
|
|
39
|
-
return size
|
50
|
+
return size + amount
|
40
51
|
LUA
|
41
52
|
|
42
53
|
@lock = Mutex.new
|
@@ -81,7 +92,7 @@ class SimpleThrottle
|
|
81
92
|
# @yieldreturn [Redis]
|
82
93
|
# @return [void]
|
83
94
|
def set_redis(client = nil, &block)
|
84
|
-
@redis_client = (client || block)
|
95
|
+
@redis_client = (client || block) # rubocop:disable Style/RedundantParentheses
|
85
96
|
end
|
86
97
|
|
87
98
|
# Return the Redis instance where the throttles are stored.
|
@@ -142,8 +153,18 @@ class SimpleThrottle
|
|
142
153
|
#
|
143
154
|
# @return [Boolean]
|
144
155
|
def allowed!
|
145
|
-
size =
|
146
|
-
size
|
156
|
+
size = add_request(1, false)
|
157
|
+
size <= limit
|
158
|
+
end
|
159
|
+
|
160
|
+
# Increment the throttle by the specified and return the current size. Because
|
161
|
+
# how the throttle is implemented in Redis, the return value will always max
|
162
|
+
# out at the throttle limit + 1 or, if the pause to recover option is set, limit + 2.
|
163
|
+
#
|
164
|
+
# @param amount [Integer] amount to increment the throttle by
|
165
|
+
# @return [Integer]
|
166
|
+
def increment!(amount = 1)
|
167
|
+
add_request(amount, true)
|
147
168
|
end
|
148
169
|
|
149
170
|
# Reset a throttle back to zero.
|
@@ -188,17 +209,19 @@ class SimpleThrottle
|
|
188
209
|
end
|
189
210
|
end
|
190
211
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
212
|
+
def redis_key
|
213
|
+
"simple_throttle.#{name}"
|
214
|
+
end
|
215
|
+
|
216
|
+
def add_request(amount, cleanup)
|
195
217
|
pause_to_recover_arg = (@pause_to_recover ? 1 : 0)
|
196
218
|
time_ms = (Time.now.to_f * 1000).round
|
197
219
|
ttl_ms = (ttl * 1000).ceil
|
198
|
-
self.class.send(
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
220
|
+
self.class.send(
|
221
|
+
:execute_lua_script,
|
222
|
+
redis: redis_client,
|
223
|
+
keys: [redis_key],
|
224
|
+
args: [limit, ttl_ms, time_ms, pause_to_recover_arg, amount, (cleanup ? 1 : 0)]
|
225
|
+
)
|
203
226
|
end
|
204
227
|
end
|
data/simple_throttle.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "simple_throttle"
|
3
|
-
spec.version = File.read(File.expand_path("
|
3
|
+
spec.version = File.read(File.expand_path("VERSION", __dir__)).strip
|
4
4
|
spec.authors = ["Brian Durand"]
|
5
5
|
spec.email = ["bbdurand@gmail.com"]
|
6
6
|
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
gemfiles/
|
21
21
|
spec/
|
22
22
|
]
|
23
|
-
spec.files = Dir.chdir(
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
24
|
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
25
25
|
end
|
26
26
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_throttle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Durand
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
70
|
- !ruby/object:Gem::Version
|
71
71
|
version: '0'
|
72
72
|
requirements: []
|
73
|
-
rubygems_version: 3.
|
73
|
+
rubygems_version: 3.4.10
|
74
74
|
signing_key:
|
75
75
|
specification_version: 4
|
76
76
|
summary: Simple redis backed throttling mechanism to limit access to a resource
|