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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e18797fda6fc1d6b7c7fec9630876cca2ddffce4c716767598d0491d763d86e
4
- data.tar.gz: f20bb9caa31be9ec0b185be99ead23d56a644e8f73662da97efa3ebd0bcac4a0
3
+ metadata.gz: 47859d64b667fe3e43c8a98ecbce2fa305177530a5d625ed0bd38a500875d268
4
+ data.tar.gz: 3c0df333efeec1abc8487328a2d131a9ffc2c87273fb5f0ec4048bc340ba1d55
5
5
  SHA512:
6
- metadata.gz: 4950eeea8f2419d800921816c7de0a4b3d120fd37462881c07bc2bb8720046b27a6ecbbc6709e314191bae5f225c3ae794cb9af2f34b2358490a4888f429ccce
7
- data.tar.gz: f8c2e359ddd7b9b429ac2863dbc63d3614a00f3f8f15b85d0f509aae89140e0ac2d880945fafa737330fccff95559fa8a2c1d6f30f7513da28945ec0a9424bdd
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
- - Fix wait_time method to match the documentation from [bc-swoop](https://github.com/bc-swoop)
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
- - Throttle insances can now specify the Redis instance to override the global setting.
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016 WHI, Inc.
1
+ Copyright (c) 2016 Brian Durand
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- [![Maintainability](https://api.codeclimate.com/v1/badges/0535eef45908cc64b740/maintainability)](https://codeclimate.com/github/weheartit/simple_throttle/maintainability)
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.6
1
+ 1.1.0
@@ -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 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
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 push > 0 and (size < limit or (size == limit and pause_to_recover > 0)) then
35
- redis.call('rpush', list_key, now)
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 = current_size(true)
146
- size < limit
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
- current_size(false)
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
@@ -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("../VERSION", __FILE__)).strip
4
- spec.authors = ["We Heart It", "Brian Durand"]
5
- spec.email = ["dev@weheartit.com", "bbdurand@gmail.com"]
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/weheartit/simple_throttle"
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(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,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.6
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: 2023-03-09 00:00:00.000000000 Z
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/weheartit/simple_throttle
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.2.22
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