simple_throttle 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +24 -0
- data/README.md +35 -1
- data/VERSION +1 -1
- data/lib/simple_throttle.rb +94 -87
- data/simple_throttle.gemspec +28 -19
- metadata +7 -41
- data/.gitignore +0 -2
- data/Rakefile +0 -18
- data/spec/simple_throttle_spec.rb +0 -56
- data/spec/spec_helper.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 25cae7f3e3702407aca9ce8b20dd4a2016cdefffd9220261a0397150b10796de
|
4
|
+
data.tar.gz: f460f12908747e7d8d5431a7ed13274fa6eec4980a60b95949d78e553b297fb0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96a6efaf9ed64b918ef077296bc1b04c36ef10f8e0e08948e505bf5203b79c4673a0435dc052c750249fb5dd7de1f9b71cab1790743aee10508bef72546bf3bc
|
7
|
+
data.tar.gz: 5dd505cf1da9506c505598013ab644367da24b9542687d5cf2e1f8a5cb571ad685388bd1dad8621598035d5810a615ce85baf65efc272aa6f1642fc637cf187b
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## 1.0.2
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Throttle insances can now specify the Redis instance to override the global setting
|
11
|
+
- Redis instance now defaults to the default redis instance: `Redis.new`
|
12
|
+
- Optimize loading LUA script to Redis; now done globally instead of per throttle instance
|
13
|
+
|
14
|
+
|
15
|
+
## 1.0.1
|
16
|
+
|
17
|
+
### Added
|
18
|
+
- Added mutex in `SimpleThrottle.add` to ensure thread safety when adding global throttles.
|
19
|
+
|
20
|
+
|
21
|
+
## 1.0.0
|
22
|
+
|
23
|
+
### Added
|
24
|
+
- Simple Redis backed throttle for Ruby.
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/0535eef45908cc64b740/maintainability)](https://codeclimate.com/github/weheartit/simple_throttle/maintainability)
|
2
|
+
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
3
|
+
|
1
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
|
2
5
|
|
3
6
|
## Usage
|
@@ -9,6 +12,9 @@ SimpleThrottle.set_redis(Redis.new)
|
|
9
12
|
# ...or provide a block that returns a redis client
|
10
13
|
SimpleThrottle.set_redis{ connection_pool.redis }
|
11
14
|
|
15
|
+
# ...or provide a Redis for a throttle to use
|
16
|
+
SimpleThrottle.new("user#{user.it}", limit: 10, ttl: 60, redis: Redis.new)
|
17
|
+
|
12
18
|
# Add a global throttle (max of 10 requests in 60 seconds)
|
13
19
|
SimpleThrottle.add(:things, limit: 10, ttl: 60)
|
14
20
|
|
@@ -32,4 +38,32 @@ Calling `allowed!` will return `true` if the throttle limit has not yet been rea
|
|
32
38
|
|
33
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.
|
34
40
|
|
35
|
-
Redis server 2.6 or greater is required.
|
41
|
+
Redis server 2.6 or greater is required.
|
42
|
+
|
43
|
+
## Installation
|
44
|
+
|
45
|
+
Add this line to your application's Gemfile:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
gem 'simple_throttle'
|
49
|
+
```
|
50
|
+
|
51
|
+
And then execute:
|
52
|
+
```bash
|
53
|
+
$ bundle
|
54
|
+
```
|
55
|
+
|
56
|
+
Or install it yourself as:
|
57
|
+
```bash
|
58
|
+
$ gem install simple_throttle
|
59
|
+
```
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
|
63
|
+
Open a pull request on GitHub.
|
64
|
+
|
65
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
66
|
+
|
67
|
+
## License
|
68
|
+
|
69
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.2
|
data/lib/simple_throttle.rb
CHANGED
@@ -1,30 +1,60 @@
|
|
1
|
-
|
2
|
-
require 'thread'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
3
|
+
require "redis"
|
4
4
|
# Create a simple throttle that can be used to limit the number of request for a resouce
|
5
5
|
# per time period. These objects are thread safe.
|
6
6
|
class SimpleThrottle
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
# Server side Lua script that maintains the throttle in redis. The throttle is stored as a list
|
8
|
+
# of timestamps in milliseconds. When the script is invoked it will scan the oldest entries
|
9
|
+
# removing any that should be expired from the list. If the list is below the specified limit
|
10
|
+
# then the current entry will be added. The list is marked to expire with the oldest entry so
|
11
|
+
# there's no need to cleanup the lists.
|
12
|
+
LUA_SCRIPT = <<~LUA
|
13
|
+
local list_key = KEYS[1]
|
14
|
+
local limit = tonumber(ARGV[1])
|
15
|
+
local ttl = tonumber(ARGV[2])
|
16
|
+
local now = ARGV[3]
|
17
|
+
local push = tonumber(ARGV[4])
|
18
|
+
|
19
|
+
local size = redis.call('llen', list_key)
|
20
|
+
if size >= limit then
|
21
|
+
local expired = tonumber(now) - ttl
|
22
|
+
while size > 0 do
|
23
|
+
local t = redis.call('lpop', list_key)
|
24
|
+
if tonumber(t) > expired then
|
25
|
+
redis.call('lpush', list_key, t)
|
26
|
+
break
|
27
|
+
end
|
28
|
+
size = size - 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if push > 0 and size < limit then
|
33
|
+
redis.call('rpush', list_key, now)
|
34
|
+
redis.call('pexpire', list_key, ttl)
|
35
|
+
end
|
36
|
+
|
37
|
+
return size
|
38
|
+
LUA
|
39
|
+
|
40
|
+
@lock = Mutex.new
|
41
|
+
|
10
42
|
class << self
|
11
43
|
# Add a global throttle that can be referenced later with the [] method.
|
12
|
-
def add(name, limit:, ttl:)
|
13
|
-
|
14
|
-
@throttles ||= {}
|
15
|
-
@throttles[name.to_s] = new(name, limit: limit, ttl: ttl)
|
16
|
-
end
|
44
|
+
def add(name, limit:, ttl:, redis: nil)
|
45
|
+
@lock.synchronize do
|
46
|
+
@throttles ||= {}
|
47
|
+
@throttles[name.to_s] = new(name, limit: limit, ttl: ttl, redis: redis)
|
48
|
+
end
|
17
49
|
end
|
18
|
-
|
50
|
+
|
19
51
|
# Returns a globally defined throttle with the specfied name.
|
20
52
|
def [](name)
|
21
53
|
if defined?(@throttles) && @throttles
|
22
54
|
@throttles[name.to_s]
|
23
|
-
else
|
24
|
-
nil
|
25
55
|
end
|
26
56
|
end
|
27
|
-
|
57
|
+
|
28
58
|
# Set the Redis instance to use for maintaining the throttle. This can either be set
|
29
59
|
# with a hard coded value or by the value yielded by a block. If the block form is used
|
30
60
|
# it will be invoked at runtime to get the instance. Use this method if your Redis instance
|
@@ -33,51 +63,66 @@ class SimpleThrottle
|
|
33
63
|
def set_redis(client = nil, &block)
|
34
64
|
@redis_client = (client || block)
|
35
65
|
end
|
36
|
-
|
66
|
+
|
37
67
|
# Return the Redis instance where the throttles are stored.
|
38
68
|
def redis
|
69
|
+
@redis_client ||= Redis.new
|
39
70
|
if @redis_client.is_a?(Proc)
|
40
71
|
@redis_client.call
|
41
72
|
else
|
42
73
|
@redis_client
|
43
74
|
end
|
44
75
|
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def execute_lua_script(redis:, keys:, args:)
|
80
|
+
@script_sha_1 ||= redis.script(:load, LUA_SCRIPT)
|
81
|
+
begin
|
82
|
+
redis.evalsha(@script_sha_1, Array(keys), Array(args))
|
83
|
+
rescue Redis::CommandError => e
|
84
|
+
if e.message.include?("NOSCRIPT")
|
85
|
+
@script_sha_1 = redis.script(:load, LUA_SCRIPT)
|
86
|
+
retry
|
87
|
+
else
|
88
|
+
raise e
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
45
92
|
end
|
46
|
-
|
93
|
+
|
47
94
|
attr_reader :name, :limit, :ttl
|
48
|
-
|
49
|
-
# Create a new throttle
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
95
|
+
|
96
|
+
# Create a new throttle
|
97
|
+
# @param name [String] unique name for the throttle
|
98
|
+
# @param ttl [Numeric] number of seconds that the throttle will remain active
|
99
|
+
# @param limit [Integer] number of allowed requests within the throttle ttl
|
100
|
+
# @param redis [Redis] Redis client to use
|
101
|
+
def initialize(name, ttl:, limit:, redis: nil)
|
53
102
|
@name = name.to_s
|
54
|
-
@name.freeze unless
|
103
|
+
@name = name.dup.freeze unless name.frozen?
|
55
104
|
@limit = limit
|
56
105
|
@ttl = ttl
|
57
|
-
@
|
106
|
+
@redis = redis
|
58
107
|
end
|
59
|
-
|
108
|
+
|
60
109
|
# Returns true if the limit for the throttle has not been reached yet. This method
|
61
110
|
# will also track the throttled resource as having been invoked on each call.
|
62
111
|
def allowed!
|
63
112
|
size = current_size(true)
|
64
|
-
|
65
|
-
true
|
66
|
-
else
|
67
|
-
false
|
68
|
-
end
|
113
|
+
size < limit
|
69
114
|
end
|
70
|
-
|
115
|
+
|
71
116
|
# Reset a throttle back to zero.
|
72
117
|
def reset!
|
73
|
-
|
118
|
+
redis_client.del(redis_key)
|
74
119
|
end
|
75
|
-
|
120
|
+
|
76
121
|
# Peek at the current number for throttled calls being tracked.
|
77
122
|
def peek
|
78
123
|
current_size(false)
|
79
124
|
end
|
80
|
-
|
125
|
+
|
81
126
|
# Returns when the next resource call should be allowed. Note that this doesn't guarantee that
|
82
127
|
# calling allow! will return true if the wait time is zero since other processes or threads can
|
83
128
|
# claim the resource.
|
@@ -85,71 +130,33 @@ class SimpleThrottle
|
|
85
130
|
if peek < limit
|
86
131
|
0.0
|
87
132
|
else
|
88
|
-
first =
|
133
|
+
first = redis_client.lindex(redis_key, 0).to_f / 1000.0
|
89
134
|
delta = Time.now.to_f - first
|
90
135
|
delta = 0.0 if delta < 0
|
91
136
|
delta
|
92
137
|
end
|
93
138
|
end
|
94
|
-
|
139
|
+
|
95
140
|
private
|
96
|
-
|
141
|
+
|
142
|
+
def redis_client
|
143
|
+
if @redis.is_a?(Proc)
|
144
|
+
@redis.call || self.class.redis
|
145
|
+
else
|
146
|
+
@redis || self.class.redis
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
97
150
|
# Evaluate and execute a Lua script on the redis server that returns the number calls currently being tracked.
|
98
151
|
# If push is set to true then a new item will be added to the list.
|
99
152
|
def current_size(push)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
time_ms = (Time.now.to_f * 1000).round
|
105
|
-
ttl_ms = ttl * 1000
|
106
|
-
redis.evalsha(@script_sha_1, [], [redis_key, limit, ttl_ms, time_ms, push_arg])
|
107
|
-
rescue Redis::CommandError => e
|
108
|
-
if e.message.include?('NOSCRIPT'.freeze)
|
109
|
-
@script_sha_1 = redis.script(:load, lua_script)
|
110
|
-
retry
|
111
|
-
else
|
112
|
-
raise e
|
113
|
-
end
|
114
|
-
end
|
153
|
+
push_arg = (push ? 1 : 0)
|
154
|
+
time_ms = (Time.now.to_f * 1000).round
|
155
|
+
ttl_ms = ttl * 1000
|
156
|
+
self.class.send(:execute_lua_script, redis: redis_client, keys: [redis_key], args: [limit, ttl_ms, time_ms, push_arg])
|
115
157
|
end
|
116
|
-
|
158
|
+
|
117
159
|
def redis_key
|
118
160
|
"simple_throttle.#{name}"
|
119
161
|
end
|
120
|
-
|
121
|
-
# Server side Lua script that maintains the throttle in redis. The throttle is stored as a list
|
122
|
-
# of timestamps in milliseconds. When the script is invoked it will scan the oldest entries
|
123
|
-
# removing any that should be expired from the list. If the list is below the specified limit
|
124
|
-
# then the current entry will be added. The list is marked to expire with the oldest entry so
|
125
|
-
# there's no need to cleanup the lists.
|
126
|
-
def lua_script
|
127
|
-
<<-LUA
|
128
|
-
local list_key = ARGV[1]
|
129
|
-
local limit = tonumber(ARGV[2])
|
130
|
-
local ttl = tonumber(ARGV[3])
|
131
|
-
local now = ARGV[4]
|
132
|
-
local push = tonumber(ARGV[5])
|
133
|
-
|
134
|
-
local size = redis.call('llen', list_key)
|
135
|
-
if size >= limit then
|
136
|
-
local expired = tonumber(now) - ttl
|
137
|
-
while size > 0 do
|
138
|
-
local t = redis.call('lpop', list_key)
|
139
|
-
if tonumber(t) > expired then
|
140
|
-
redis.call('lpush', list_key, t)
|
141
|
-
break
|
142
|
-
end
|
143
|
-
size = size - 1
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
if push > 0 and size < limit then
|
148
|
-
redis.call('rpush', list_key, now)
|
149
|
-
redis.call('pexpire', list_key, ttl)
|
150
|
-
end
|
151
|
-
|
152
|
-
return size
|
153
|
-
LUA
|
154
|
-
end
|
155
162
|
end
|
data/simple_throttle.gemspec
CHANGED
@@ -1,25 +1,34 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
|
5
1
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
10
|
-
|
11
|
-
spec.
|
12
|
-
spec.homepage
|
13
|
-
spec.license
|
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"]
|
6
|
+
|
7
|
+
spec.summary = "Simple redis backed throttling mechanism to limit access to a resource"
|
8
|
+
spec.homepage = "https://github.com/weheartit/simple_throttle"
|
9
|
+
spec.license = "MIT"
|
10
|
+
|
11
|
+
# Specify which files should be added to the gem when it is released.
|
12
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
|
+
ignore_files = %w[
|
14
|
+
.
|
15
|
+
Appraisals
|
16
|
+
Gemfile
|
17
|
+
Gemfile.lock
|
18
|
+
Rakefile
|
19
|
+
bin/
|
20
|
+
gemfiles/
|
21
|
+
spec/
|
22
|
+
]
|
23
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
25
|
+
end
|
14
26
|
|
15
|
-
spec.files = `git ls-files`.split($/)
|
16
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
27
|
spec.require_paths = ["lib"]
|
19
28
|
|
20
|
-
spec.add_dependency
|
29
|
+
spec.add_dependency "redis"
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler"
|
21
32
|
|
22
|
-
spec.
|
23
|
-
spec.add_development_dependency "rake"
|
24
|
-
spec.add_development_dependency "rspec"
|
33
|
+
spec.required_ruby_version = ">= 2.5"
|
25
34
|
end
|
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.
|
4
|
+
version: 1.0.2
|
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:
|
12
|
+
date: 2021-09-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -27,20 +27,6 @@ dependencies:
|
|
27
27
|
version: '0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: bundler
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - "~>"
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '1.3'
|
35
|
-
type: :development
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - "~>"
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '1.3'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: rake
|
44
30
|
requirement: !ruby/object:Gem::Requirement
|
45
31
|
requirements:
|
46
32
|
- - ">="
|
@@ -53,21 +39,7 @@ dependencies:
|
|
53
39
|
- - ">="
|
54
40
|
- !ruby/object:Gem::Version
|
55
41
|
version: '0'
|
56
|
-
|
57
|
-
name: rspec
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
description: Simple redis backed throttling mechanism to limit access to a resource.
|
42
|
+
description:
|
71
43
|
email:
|
72
44
|
- dev@weheartit.com
|
73
45
|
- bbdurand@gmail.com
|
@@ -75,15 +47,12 @@ executables: []
|
|
75
47
|
extensions: []
|
76
48
|
extra_rdoc_files: []
|
77
49
|
files:
|
78
|
-
-
|
50
|
+
- CHANGELOG.md
|
79
51
|
- MIT_LICENSE.txt
|
80
52
|
- README.md
|
81
|
-
- Rakefile
|
82
53
|
- VERSION
|
83
54
|
- lib/simple_throttle.rb
|
84
55
|
- simple_throttle.gemspec
|
85
|
-
- spec/simple_throttle_spec.rb
|
86
|
-
- spec/spec_helper.rb
|
87
56
|
homepage: https://github.com/weheartit/simple_throttle
|
88
57
|
licenses:
|
89
58
|
- MIT
|
@@ -96,18 +65,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
65
|
requirements:
|
97
66
|
- - ">="
|
98
67
|
- !ruby/object:Gem::Version
|
99
|
-
version: '
|
68
|
+
version: '2.5'
|
100
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
70
|
requirements:
|
102
71
|
- - ">="
|
103
72
|
- !ruby/object:Gem::Version
|
104
73
|
version: '0'
|
105
74
|
requirements: []
|
106
|
-
|
107
|
-
rubygems_version: 2.6.12
|
75
|
+
rubygems_version: 3.0.3
|
108
76
|
signing_key:
|
109
77
|
specification_version: 4
|
110
78
|
summary: Simple redis backed throttling mechanism to limit access to a resource
|
111
|
-
test_files:
|
112
|
-
- spec/simple_throttle_spec.rb
|
113
|
-
- spec/spec_helper.rb
|
79
|
+
test_files: []
|
data/.gitignore
DELETED
data/Rakefile
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
2
|
-
|
3
|
-
desc 'Default: run unit tests.'
|
4
|
-
task :default => :test
|
5
|
-
|
6
|
-
desc 'RVM likes to call it tests'
|
7
|
-
task :tests => :test
|
8
|
-
|
9
|
-
begin
|
10
|
-
require 'rspec'
|
11
|
-
require 'rspec/core/rake_task'
|
12
|
-
desc 'Run the unit tests'
|
13
|
-
RSpec::Core::RakeTask.new(:test)
|
14
|
-
rescue LoadError
|
15
|
-
task :test do
|
16
|
-
STDERR.puts "You must have rspec 2.0 installed to run the tests"
|
17
|
-
end
|
18
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe SimpleThrottle do
|
4
|
-
|
5
|
-
it "should tell if a call is allowed" do
|
6
|
-
throttle = SimpleThrottle.new("test_simple_throttle", limit: 3, ttl: 1)
|
7
|
-
throttle.reset!
|
8
|
-
other_throttle = SimpleThrottle.new("test_simple_throttle_2", limit: 3, ttl: 1)
|
9
|
-
other_throttle.reset!
|
10
|
-
|
11
|
-
expect(throttle.peek).to eq 0
|
12
|
-
expect(throttle.allowed!).to eq true
|
13
|
-
expect(throttle.peek).to eq 1
|
14
|
-
expect(throttle.allowed!).to eq true
|
15
|
-
expect(throttle.peek).to eq 2
|
16
|
-
expect(throttle.allowed!).to eq true
|
17
|
-
expect(throttle.peek).to eq 3
|
18
|
-
expect(throttle.allowed!).to eq false
|
19
|
-
expect(throttle.peek).to eq 3
|
20
|
-
expect(throttle.allowed!).to eq false
|
21
|
-
wait_time = throttle.wait_time
|
22
|
-
expect(wait_time).to be > 0.0
|
23
|
-
expect(wait_time).to be <= 1.0
|
24
|
-
|
25
|
-
expect(other_throttle.allowed!).to eq true
|
26
|
-
expect(other_throttle.peek).to eq 1
|
27
|
-
expect(other_throttle.wait_time).to eq 0.0
|
28
|
-
|
29
|
-
sleep(1.1)
|
30
|
-
|
31
|
-
expect(throttle.allowed!).to eq true
|
32
|
-
sleep(0.3)
|
33
|
-
expect(throttle.allowed!).to eq true
|
34
|
-
sleep(0.3)
|
35
|
-
expect(throttle.allowed!).to eq true
|
36
|
-
sleep(0.3)
|
37
|
-
expect(throttle.allowed!).to eq false
|
38
|
-
sleep(0.3)
|
39
|
-
expect(throttle.allowed!).to eq true
|
40
|
-
expect(throttle.allowed!).to eq false
|
41
|
-
end
|
42
|
-
|
43
|
-
it "should be able to add global throttles" do
|
44
|
-
SimpleThrottle.add(:test_1, limit: 4, ttl: 60)
|
45
|
-
SimpleThrottle.add(:test_2, limit: 10, ttl: 3600)
|
46
|
-
t1 = SimpleThrottle["test_1"]
|
47
|
-
expect(t1.name).to eq "test_1"
|
48
|
-
expect(t1.limit).to eq 4
|
49
|
-
expect(t1.ttl).to eq 60
|
50
|
-
t1 = SimpleThrottle[:test_2]
|
51
|
-
expect(t1.name).to eq "test_2"
|
52
|
-
expect(t1.limit).to eq 10
|
53
|
-
expect(t1.ttl).to eq 3600
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require File.expand_path('../../lib/simple_throttle', __FILE__)
|
2
|
-
|
3
|
-
SimpleThrottle.set_redis(Redis.new)
|
4
|
-
|
5
|
-
RSpec.configure do |config|
|
6
|
-
config.run_all_when_everything_filtered = true
|
7
|
-
config.filter_run :focus
|
8
|
-
|
9
|
-
# Run specs in random order to surface order dependencies. If you find an
|
10
|
-
# order dependency and want to debug it, you can fix the order by providing
|
11
|
-
# the seed, which is printed after each run.
|
12
|
-
# --seed 1234
|
13
|
-
config.order = 'random'
|
14
|
-
end
|