simple_throttle 1.0.4 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +28 -2
- data/VERSION +1 -1
- data/lib/simple_throttle.rb +22 -9
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e18797fda6fc1d6b7c7fec9630876cca2ddffce4c716767598d0491d763d86e
|
4
|
+
data.tar.gz: f20bb9caa31be9ec0b185be99ead23d56a644e8f73662da97efa3ebd0bcac4a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4950eeea8f2419d800921816c7de0a4b3d120fd37462881c07bc2bb8720046b27a6ecbbc6709e314191bae5f225c3ae794cb9af2f34b2358490a4888f429ccce
|
7
|
+
data.tar.gz: f8c2e359ddd7b9b429ac2863dbc63d3614a00f3f8f15b85d0f509aae89140e0ac2d880945fafa737330fccff95559fa8a2c1d6f30f7513da28945ec0a9424bdd
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,20 @@ 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
|
+
|
12
|
+
## 1.0.5
|
13
|
+
|
14
|
+
### Added
|
15
|
+
- Make loading lua script play better with Redis clusters.
|
16
|
+
- Handle failures loading lua script on Redis server to prevent infinite loop.
|
17
|
+
|
7
18
|
## 1.0.4
|
19
|
+
|
20
|
+
### Fixed
|
8
21
|
- Fix wait_time method to match the documentation from [bc-swoop](https://github.com/bc-swoop)
|
9
22
|
|
10
23
|
## 1.0.3
|
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
|
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
|
-
|
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.
|
1
|
+
1.0.6
|
data/lib/simple_throttle.rb
CHANGED
@@ -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
|
|
@@ -95,12 +99,16 @@ class SimpleThrottle
|
|
95
99
|
private
|
96
100
|
|
97
101
|
def execute_lua_script(redis:, keys:, args:)
|
98
|
-
|
102
|
+
client = redis
|
103
|
+
@script_sha_1 ||= client.script(:load, LUA_SCRIPT)
|
104
|
+
attempts = 0
|
105
|
+
|
99
106
|
begin
|
100
|
-
|
107
|
+
client.evalsha(@script_sha_1, Array(keys), Array(args))
|
101
108
|
rescue Redis::CommandError => e
|
102
|
-
if e.message.include?("NOSCRIPT")
|
103
|
-
@script_sha_1 =
|
109
|
+
if e.message.include?("NOSCRIPT") && attempts < 2
|
110
|
+
@script_sha_1 = client.script(:load, LUA_SCRIPT)
|
111
|
+
attempts += 1
|
104
112
|
retry
|
105
113
|
else
|
106
114
|
raise e
|
@@ -116,12 +124,16 @@ class SimpleThrottle
|
|
116
124
|
# @param name [String] unique name for the throttle
|
117
125
|
# @param ttl [Numeric] number of seconds that the throttle will remain active
|
118
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.
|
119
130
|
# @param redis [Redis, Proc] Redis instance to use or a Proc that yields a Redos instance
|
120
|
-
def initialize(name, ttl:, limit:, redis: nil)
|
131
|
+
def initialize(name, ttl:, limit:, pause_to_recover: false, redis: nil)
|
121
132
|
@name = name.to_s
|
122
133
|
@name = name.dup.freeze unless name.frozen?
|
123
134
|
@limit = limit.to_i
|
124
135
|
@ttl = ttl.to_f
|
136
|
+
@pause_to_recover = !!pause_to_recover
|
125
137
|
@redis = redis
|
126
138
|
end
|
127
139
|
|
@@ -178,9 +190,10 @@ class SimpleThrottle
|
|
178
190
|
# If push is set to true then a new item will be added to the list.
|
179
191
|
def current_size(push)
|
180
192
|
push_arg = (push ? 1 : 0)
|
193
|
+
pause_to_recover_arg = (@pause_to_recover ? 1 : 0)
|
181
194
|
time_ms = (Time.now.to_f * 1000).round
|
182
195
|
ttl_ms = (ttl * 1000).ceil
|
183
|
-
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])
|
184
197
|
end
|
185
198
|
|
186
199
|
def redis_key
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
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.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- We Heart It
|
8
8
|
- Brian Durand
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-03-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- - ">="
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
|
-
description:
|
42
|
+
description:
|
43
43
|
email:
|
44
44
|
- dev@weheartit.com
|
45
45
|
- bbdurand@gmail.com
|
@@ -57,7 +57,7 @@ homepage: https://github.com/weheartit/simple_throttle
|
|
57
57
|
licenses:
|
58
58
|
- MIT
|
59
59
|
metadata: {}
|
60
|
-
post_install_message:
|
60
|
+
post_install_message:
|
61
61
|
rdoc_options: []
|
62
62
|
require_paths:
|
63
63
|
- lib
|
@@ -72,8 +72,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
72
|
- !ruby/object:Gem::Version
|
73
73
|
version: '0'
|
74
74
|
requirements: []
|
75
|
-
rubygems_version: 3.
|
76
|
-
signing_key:
|
75
|
+
rubygems_version: 3.2.22
|
76
|
+
signing_key:
|
77
77
|
specification_version: 4
|
78
78
|
summary: Simple redis backed throttling mechanism to limit access to a resource
|
79
79
|
test_files: []
|