simple_throttle 1.0.5 → 1.0.6

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: 30c315dd33f7e175c0e74b4dfd8344e889d7e856c96c786b87ebac29d07fcc3a
4
- data.tar.gz: acfb600e437eb125b9bf279dd95315ce7fa40612f836f887003b360ae2aacceb
3
+ metadata.gz: 8e18797fda6fc1d6b7c7fec9630876cca2ddffce4c716767598d0491d763d86e
4
+ data.tar.gz: f20bb9caa31be9ec0b185be99ead23d56a644e8f73662da97efa3ebd0bcac4a0
5
5
  SHA512:
6
- metadata.gz: 89d1b64043ee7f6460be93bb12ac50728e31ede360bcaa68191cb84087d50fe7db4386d8d24eba380c37a9426df78cb26c0685da75d7f6610e244e332be2821c
7
- data.tar.gz: 47598bdce624ab1effbefbfc36d8860f3e017a7ecb2213cdd46d8c67e681583df62bdaf1270f77f6d4e83a88de0c0222c92c3a141fb9bfc3340cbee3bf0914a3
6
+ metadata.gz: 4950eeea8f2419d800921816c7de0a4b3d120fd37462881c07bc2bb8720046b27a6ecbbc6709e314191bae5f225c3ae794cb9af2f34b2358490a4888f429ccce
7
+ data.tar.gz: f8c2e359ddd7b9b429ac2863dbc63d3614a00f3f8f15b85d0f509aae89140e0ac2d880945fafa737330fccff95559fa8a2c1d6f30f7513da28945ec0a9424bdd
data/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ 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.0.6
8
+
9
+ ### Added
10
+ - 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
+
7
12
  ## 1.0.5
8
13
 
9
14
  ### Added
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  [![Maintainability](https://api.codeclimate.com/v1/badges/0535eef45908cc64b740/maintainability)](https://codeclimate.com/github/weheartit/simple_throttle/maintainability)
2
2
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
3
3
 
4
- 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). These
4
+ 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).
5
5
 
6
6
  ## Usage
7
7
 
@@ -38,7 +38,33 @@ Calling `allowed!` will return `true` if the throttle limit has not yet been rea
38
38
 
39
39
  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
40
 
41
- Redis server 2.6 or greater is required.
41
+ ### Pause to recover option
42
+
43
+ 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.
44
+
45
+ For example, if you have a throttle that allows 10 calls per 60 seconds, then a process hitting every second will succeed 10 times per minute. A similar throttle with the `pause_to_recover` option set, would only succeed on the first 10 calls. After that, it will continue to fail until the rate at which it is called drops below the maximum rate of the throttle (i.e. once every 6 seconds).
46
+
47
+ ```ruby
48
+ throttle_1 = SimpleThrottle.new("t1", limit: 10, ttl: 60)
49
+ throttle_2 = SimpleThrottle.new("t2", limit: 10, ttl: 60, pause_to_recover: true)
50
+
51
+ loop do
52
+ if throttle_1.allowed!
53
+ # This will be called 10 times every minute.
54
+ do_thing_1
55
+ end
56
+
57
+ if throttle_2.allowed!
58
+ # This will only be called 10 times in total because the throttle is never
59
+ # given a chance to recover.
60
+ do_thing_2
61
+ end
62
+ end
63
+ ```
64
+
65
+ ### Redis requirement
66
+
67
+ Redis server 2.6 or greater is required for this code.
42
68
 
43
69
  ## Installation
44
70
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.5
1
+ 1.0.6
@@ -16,6 +16,7 @@ class SimpleThrottle
16
16
  local ttl = tonumber(ARGV[2])
17
17
  local now = ARGV[3]
18
18
  local push = tonumber(ARGV[4])
19
+ local pause_to_recover = tonumber(ARGV[5])
19
20
 
20
21
  local size = redis.call('llen', list_key)
21
22
  if size >= limit then
@@ -30,7 +31,7 @@ class SimpleThrottle
30
31
  end
31
32
  end
32
33
 
33
- if push > 0 and size < limit then
34
+ if push > 0 and (size < limit or (size == limit and pause_to_recover > 0)) then
34
35
  redis.call('rpush', list_key, now)
35
36
  redis.call('pexpire', list_key, ttl)
36
37
  end
@@ -48,12 +49,15 @@ class SimpleThrottle
48
49
  # @param name [String] unique name for the throttle
49
50
  # @param ttl [Numeric] number of seconds that the throttle will remain active
50
51
  # @param limit [Integer] number of allowed requests within the throttle ttl
52
+ # @param pause_to_recover [Boolean] require processes calling the throttle
53
+ # to pause at least temporarily before freeing up the throttle. If this is true,
54
+ # then a throttle called constantly with no pauses will never free up.
51
55
  # @param redis [Redis, Proc] Redis instance to use or a Proc that yields a Redos instance
52
56
  # @return [void]
53
- def add(name, ttl:, limit:, redis: nil)
57
+ def add(name, ttl:, limit:, pause_to_recover: false, redis: nil)
54
58
  @lock.synchronize do
55
59
  @throttles ||= {}
56
- @throttles[name.to_s] = new(name, limit: limit, ttl: ttl, redis: redis)
60
+ @throttles[name.to_s] = new(name, limit: limit, ttl: ttl, pause_to_recover: pause_to_recover, redis: redis)
57
61
  end
58
62
  end
59
63
 
@@ -120,12 +124,16 @@ class SimpleThrottle
120
124
  # @param name [String] unique name for the throttle
121
125
  # @param ttl [Numeric] number of seconds that the throttle will remain active
122
126
  # @param limit [Integer] number of allowed requests within the throttle ttl
127
+ # @param pause_to_recover [Boolean] require processes calling the throttle
128
+ # to pause at least temporarily before freeing up the throttle. If this is true,
129
+ # then a throttle called constantly with no pauses will never free up.
123
130
  # @param redis [Redis, Proc] Redis instance to use or a Proc that yields a Redos instance
124
- def initialize(name, ttl:, limit:, redis: nil)
131
+ def initialize(name, ttl:, limit:, pause_to_recover: false, redis: nil)
125
132
  @name = name.to_s
126
133
  @name = name.dup.freeze unless name.frozen?
127
134
  @limit = limit.to_i
128
135
  @ttl = ttl.to_f
136
+ @pause_to_recover = !!pause_to_recover
129
137
  @redis = redis
130
138
  end
131
139
 
@@ -182,9 +190,10 @@ class SimpleThrottle
182
190
  # If push is set to true then a new item will be added to the list.
183
191
  def current_size(push)
184
192
  push_arg = (push ? 1 : 0)
193
+ pause_to_recover_arg = (@pause_to_recover ? 1 : 0)
185
194
  time_ms = (Time.now.to_f * 1000).round
186
195
  ttl_ms = (ttl * 1000).ceil
187
- self.class.send(:execute_lua_script, redis: redis_client, keys: [redis_key], args: [limit, ttl_ms, time_ms, push_arg])
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])
188
197
  end
189
198
 
190
199
  def redis_key
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_throttle
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - We Heart It
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-03-04 00:00:00.000000000 Z
12
+ date: 2023-03-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis