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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bf38f023dce00a87b27e9b2f39d0519a8dbb4abd90b549697be21ed6792bd7f
4
- data.tar.gz: 35023bd729899add113084518e63f8ee6975bf71c7a5b12fc0ceb64924ccd207
3
+ metadata.gz: 6777d3382abb6c7e07c503e56676a8fe62be43611b2c623ac18f89928708572e
4
+ data.tar.gz: da8f2432ec4bc9dcf68e42faaf6213d662379c3ded9dfb16c1eb8c2cd27cb62c
5
5
  SHA512:
6
- metadata.gz: dfab9a31ebc150ceb5f6b8833efc670357eefa790e60b33156607f7d72155566c300c454fc58368cae15936b8152f2c47e5ed83875ca3b8e0f58f9219d03d5e1
7
- data.tar.gz: 4a3bd5605206b8b1e93bcc527c7d3f2dbf22590f23e390c4cb5c75ba7ac3cbe2fef885adffb62006fed54f310eea5804e10c6f319c81c45c8832971d36818718
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
- - Fix wait_time method to match the documentation from [bc-swoop](https://github.com/bc-swoop)
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
- - Throttle insances can now specify the Redis instance to override the global setting.
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.0.7
1
+ 1.1.1
@@ -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 push = tonumber(ARGV[4])
19
- local pause_to_recover = tonumber(ARGV[5])
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 push > 0 and (size < limit or (size == limit and pause_to_recover > 0)) then
35
- redis.call('rpush', list_key, now)
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 = current_size(true)
146
- size < limit
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
- # Evaluate and execute a Lua script on the redis server that returns the number calls currently being tracked.
192
- # If push is set to true then a new item will be added to the list.
193
- def current_size(push)
194
- push_arg = (push ? 1 : 0)
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(:execute_lua_script, redis: redis_client, keys: [redis_key], args: [limit, ttl_ms, time_ms, push_arg, pause_to_recover_arg])
199
- end
200
-
201
- def redis_key
202
- "simple_throttle.#{name}"
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
@@ -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("../VERSION", __FILE__)).strip
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(File.expand_path("..", __FILE__)) do
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.0.7
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: 2023-03-31 00:00:00.000000000 Z
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.2.22
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