simple_throttle 1.0.6 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -2
- data/MIT_LICENSE.txt +1 -1
- data/README.md +15 -1
- data/VERSION +1 -1
- data/lib/simple_throttle.rb +39 -19
- data/simple_throttle.gemspec +5 -5
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47859d64b667fe3e43c8a98ecbce2fa305177530a5d625ed0bd38a500875d268
|
4
|
+
data.tar.gz: 3c0df333efeec1abc8487328a2d131a9ffc2c87273fb5f0ec4048bc340ba1d55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88fb81aaa2ac35d7ae5932ccded080c05fe3dd718bcfa9d73b93e8cfd3eb1706b4b5da42b02a06bcd64c861de6f3c98df9bba6005d1b104881b0d5a91f111d06
|
7
|
+
data.tar.gz: b8a94b2c2a24b7c88266a70f052a93a0a6a0fac5b48842e2fd89e56200ebf72c0229defca71ee775cc4a72810a5d4c9c8ee4feac37ab8281d8e67a0a924bf311
|
data/CHANGELOG.md
CHANGED
@@ -4,31 +4,52 @@ 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.0
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Added `increment!` method to increment the throttle by a given amount and return the current count.
|
12
|
+
|
13
|
+
## 1.0.7
|
14
|
+
|
15
|
+
## Fixed
|
16
|
+
|
17
|
+
- Fixed peek method to return the correct value rather than the raw value from Redis.
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
- Updated repository location and gem owners
|
22
|
+
|
7
23
|
## 1.0.6
|
8
24
|
|
9
25
|
### Added
|
26
|
+
|
10
27
|
- 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.
|
11
28
|
|
12
29
|
## 1.0.5
|
13
30
|
|
14
31
|
### Added
|
32
|
+
|
15
33
|
- Make loading lua script play better with Redis clusters.
|
16
34
|
- Handle failures loading lua script on Redis server to prevent infinite loop.
|
17
35
|
|
18
36
|
## 1.0.4
|
19
37
|
|
20
38
|
### Fixed
|
21
|
-
|
39
|
+
|
40
|
+
- Fix `wait_time` method to match the documentation from [bc-swoop](https://github.com/bc-swoop)
|
22
41
|
|
23
42
|
## 1.0.3
|
24
43
|
|
25
44
|
### Changed
|
45
|
+
|
26
46
|
- Ensure that arguments sent to Redis Lua script are cast to integers.
|
27
47
|
|
28
48
|
## 1.0.2
|
29
49
|
|
30
50
|
### Added
|
31
|
-
|
51
|
+
|
52
|
+
- Throttle instances can now specify the Redis instance to override the global setting.
|
32
53
|
- Redis instance now defaults to the default redis instance: `Redis.new`.
|
33
54
|
- Optimize loading LUA script to Redis; now done globally instead of per throttle instance.
|
34
55
|
|
@@ -36,10 +57,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
36
57
|
## 1.0.1
|
37
58
|
|
38
59
|
### Added
|
60
|
+
|
39
61
|
- Added mutex in `SimpleThrottle.add` to ensure thread safety when adding global throttles.
|
40
62
|
|
41
63
|
|
42
64
|
## 1.0.0
|
43
65
|
|
44
66
|
### Added
|
67
|
+
|
45
68
|
- Simple Redis backed throttle for Ruby.
|
data/MIT_LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
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)
|
2
5
|
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
3
6
|
|
4
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).
|
@@ -38,6 +41,17 @@ Calling `allowed!` will return `true` if the throttle limit has not yet been rea
|
|
38
41
|
|
39
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.
|
40
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
|
+
|
41
55
|
### Pause to recover option
|
42
56
|
|
43
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.0
|
1
|
+
1.1.0
|
data/lib/simple_throttle.rb
CHANGED
@@ -15,8 +15,8 @@ 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
20
|
|
21
21
|
local size = redis.call('llen', list_key)
|
22
22
|
if size >= limit then
|
@@ -31,12 +31,22 @@ class SimpleThrottle
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
if
|
35
|
-
|
34
|
+
if pause_to_recover > 0 then
|
35
|
+
limit = limit + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
if size + amount > limit then
|
39
|
+
amount = (limit - size) + 1
|
40
|
+
end
|
41
|
+
|
42
|
+
if size < limit then
|
43
|
+
for i = 1, amount do
|
44
|
+
redis.call('rpush', list_key, now)
|
45
|
+
end
|
36
46
|
redis.call('pexpire', list_key, ttl)
|
37
47
|
end
|
38
48
|
|
39
|
-
return size
|
49
|
+
return size + amount
|
40
50
|
LUA
|
41
51
|
|
42
52
|
@lock = Mutex.new
|
@@ -81,7 +91,7 @@ class SimpleThrottle
|
|
81
91
|
# @yieldreturn [Redis]
|
82
92
|
# @return [void]
|
83
93
|
def set_redis(client = nil, &block)
|
84
|
-
@redis_client = (client || block)
|
94
|
+
@redis_client = (client || block) # rubocop:disable Style/RedundantParentheses
|
85
95
|
end
|
86
96
|
|
87
97
|
# Return the Redis instance where the throttles are stored.
|
@@ -142,8 +152,26 @@ class SimpleThrottle
|
|
142
152
|
#
|
143
153
|
# @return [Boolean]
|
144
154
|
def allowed!
|
145
|
-
size =
|
146
|
-
size
|
155
|
+
size = increment!
|
156
|
+
size <= limit
|
157
|
+
end
|
158
|
+
|
159
|
+
# Increment the throttle by the specified and return the current size. Because
|
160
|
+
# how the throttle is implemented in Redis, the return value will always max
|
161
|
+
# out at the throttle limit + 1 or, if the pause to recover option is set, limit + 2.
|
162
|
+
#
|
163
|
+
# @param amount [Integer] amount to increment the throttle by
|
164
|
+
# @return [Integer]
|
165
|
+
def increment!(amount = 1)
|
166
|
+
pause_to_recover_arg = (@pause_to_recover ? 1 : 0)
|
167
|
+
time_ms = (Time.now.to_f * 1000).round
|
168
|
+
ttl_ms = (ttl * 1000).ceil
|
169
|
+
self.class.send(
|
170
|
+
:execute_lua_script,
|
171
|
+
redis: redis_client,
|
172
|
+
keys: [redis_key],
|
173
|
+
args: [limit, ttl_ms, time_ms, pause_to_recover_arg, amount]
|
174
|
+
)
|
147
175
|
end
|
148
176
|
|
149
177
|
# Reset a throttle back to zero.
|
@@ -157,7 +185,9 @@ class SimpleThrottle
|
|
157
185
|
#
|
158
186
|
# @return [Integer]
|
159
187
|
def peek
|
160
|
-
|
188
|
+
timestamps = redis_client.lrange(redis_key, 0, -1).collect(&:to_i)
|
189
|
+
min_timestamp = ((Time.now.to_f - ttl) * 1000).ceil
|
190
|
+
timestamps.count { |t| t > min_timestamp }
|
161
191
|
end
|
162
192
|
|
163
193
|
# Returns when the next resource call should be allowed. Note that this doesn't guarantee that
|
@@ -186,16 +216,6 @@ class SimpleThrottle
|
|
186
216
|
end
|
187
217
|
end
|
188
218
|
|
189
|
-
# Evaluate and execute a Lua script on the redis server that returns the number calls currently being tracked.
|
190
|
-
# If push is set to true then a new item will be added to the list.
|
191
|
-
def current_size(push)
|
192
|
-
push_arg = (push ? 1 : 0)
|
193
|
-
pause_to_recover_arg = (@pause_to_recover ? 1 : 0)
|
194
|
-
time_ms = (Time.now.to_f * 1000).round
|
195
|
-
ttl_ms = (ttl * 1000).ceil
|
196
|
-
self.class.send(:execute_lua_script, redis: redis_client, keys: [redis_key], args: [limit, ttl_ms, time_ms, push_arg, pause_to_recover_arg])
|
197
|
-
end
|
198
|
-
|
199
219
|
def redis_key
|
200
220
|
"simple_throttle.#{name}"
|
201
221
|
end
|
data/simple_throttle.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "simple_throttle"
|
3
|
-
spec.version = File.read(File.expand_path("
|
4
|
-
spec.authors = ["
|
5
|
-
spec.email = ["
|
3
|
+
spec.version = File.read(File.expand_path("VERSION", __dir__)).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
6
|
|
7
7
|
spec.summary = "Simple redis backed throttling mechanism to limit access to a resource"
|
8
|
-
spec.homepage = "https://github.com/
|
8
|
+
spec.homepage = "https://github.com/bdurand/simple_throttle"
|
9
9
|
spec.license = "MIT"
|
10
10
|
|
11
11
|
# Specify which files should be added to the gem when it is released.
|
@@ -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,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_throttle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- We Heart It
|
8
7
|
- Brian Durand
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2024-01-30 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: redis
|
@@ -41,7 +40,6 @@ dependencies:
|
|
41
40
|
version: '0'
|
42
41
|
description:
|
43
42
|
email:
|
44
|
-
- dev@weheartit.com
|
45
43
|
- bbdurand@gmail.com
|
46
44
|
executables: []
|
47
45
|
extensions: []
|
@@ -53,7 +51,7 @@ files:
|
|
53
51
|
- VERSION
|
54
52
|
- lib/simple_throttle.rb
|
55
53
|
- simple_throttle.gemspec
|
56
|
-
homepage: https://github.com/
|
54
|
+
homepage: https://github.com/bdurand/simple_throttle
|
57
55
|
licenses:
|
58
56
|
- MIT
|
59
57
|
metadata: {}
|
@@ -72,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
70
|
- !ruby/object:Gem::Version
|
73
71
|
version: '0'
|
74
72
|
requirements: []
|
75
|
-
rubygems_version: 3.
|
73
|
+
rubygems_version: 3.4.10
|
76
74
|
signing_key:
|
77
75
|
specification_version: 4
|
78
76
|
summary: Simple redis backed throttling mechanism to limit access to a resource
|