slow_down 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.env.example +1 -0
- data/.gitignore +12 -0
- data/.travis.yml +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +208 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/slow_down.rb +52 -0
- data/lib/slow_down/configuration.rb +110 -0
- data/lib/slow_down/group.rb +87 -0
- data/lib/slow_down/strategy/base.rb +22 -0
- data/lib/slow_down/strategy/fibonacci.rb +13 -0
- data/lib/slow_down/strategy/inverse_exponential.rb +13 -0
- data/lib/slow_down/strategy/linear.rb +11 -0
- data/lib/slow_down/version.rb +3 -0
- data/slow_down.gemspec +31 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f3bc1c5bba3ef0e9fbfff81f707ffad221dabe7a
|
4
|
+
data.tar.gz: 25f083fd0b15162bb14e3e0bd45e06ae3e3bfab2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d201bf4b9b60ccacfdcf64ff2281f4611a6e337d3f43be470230b5cd051bc3f97ee5841caa9d7c61628c014bdc9349e5f48d0dc808719d02d588dae83885b371
|
7
|
+
data.tar.gz: 3a32238090ac44cd91b1626edbd141817c8d4d0746014c708a661126ad8fae6f67007a4d4afa257dbe2c8f48addea5648407ab897f66a40e9434ec3563f6b877
|
data/.env.example
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
REDIS_URL=
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.2.1
|
4
|
+
- 2.1.5
|
5
|
+
- 2.0.0
|
6
|
+
- jruby-head
|
7
|
+
- jruby-9.0.0.0.pre1
|
8
|
+
services:
|
9
|
+
- redis-server
|
10
|
+
env:
|
11
|
+
global:
|
12
|
+
- REDIS_URL="redis://127.0.0.1:6379/0"
|
13
|
+
- JRUBY_OPTS="$JRUBY_OPTS -Xcext.enabled=false -Xcompile.invokedynamic=false"
|
14
|
+
matrix:
|
15
|
+
allow_failures:
|
16
|
+
- rvm: jruby-head
|
17
|
+
- rvm: jruby-9.0.0.0.pre1
|
18
|
+
notifications:
|
19
|
+
email: false
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Florin Lipan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# SlowDown
|
2
|
+
|
3
|
+
[](https://travis-ci.org/lipanski/slow-down)
|
4
|
+
|
5
|
+
## Why would you want to slow down your requests?!
|
6
|
+
|
7
|
+
Some external APIs might be throttling your requests (or web scraping attempts) or your own infrastructure is not able to bear the load.
|
8
|
+
It sometimes pays off to be patient...
|
9
|
+
|
10
|
+
**SlowDown** delays a call up until the point where you can afford to trigger it.
|
11
|
+
It relies on a Redis lock so it should be able to handle a cluster of servers all going for the same resource.
|
12
|
+
It's based on the `PX` and `NX` options of the Redis `SET` command, which should make it thread-safe.
|
13
|
+
Note that these options were introduced with Redis version 2.6.12.
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
### Basic
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require "slow_down"
|
21
|
+
|
22
|
+
SlowDown.config do |c|
|
23
|
+
c.requests_per_second = 10
|
24
|
+
c.retries = 50 # times
|
25
|
+
c.timeout = 5 # seconds
|
26
|
+
c.raise_on_timeout = true # will raise SlowDown::Timeout
|
27
|
+
c.redis_url = "redis://localhost:6379/0" # or set the REDIS_URL environment variable
|
28
|
+
end
|
29
|
+
|
30
|
+
SlowDown.run do
|
31
|
+
some_throttled_api_call # accepting only 10 req/sec
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
### Groups
|
36
|
+
|
37
|
+
**SlowDown** can be configured for individual groups, which can be run in isolation:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
SlowDown.config(:github) do |c|
|
41
|
+
c.requests_per_second = 50
|
42
|
+
c.timeout = 10
|
43
|
+
end
|
44
|
+
|
45
|
+
SlowDown.config(:twitter) do |c|
|
46
|
+
c.requests_per_second = 10
|
47
|
+
c.timeout = 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# Acquire a lock for the :github group
|
51
|
+
SlowDown.run(:github) { ... }
|
52
|
+
|
53
|
+
# Acquire a lock for the :twitter group
|
54
|
+
SlowDown.run(:twitter) { ... }
|
55
|
+
```
|
56
|
+
|
57
|
+
### Retrieve configuration
|
58
|
+
|
59
|
+
When called without a block, `SlowDown.config` will return the configuration of the *default* group.
|
60
|
+
In order to fetch the configuration of a different group use `SlowDown.config(:group_name)`.
|
61
|
+
|
62
|
+
### Inline configuration
|
63
|
+
|
64
|
+
**SlowDown** may also be configured directly within the `SlowDown.run` call:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
# Configure the :default group and run a call
|
68
|
+
SlowDown.run(requests_per_second: 5, timeout: 15, raise_on_timeout: true) do
|
69
|
+
# ...
|
70
|
+
end
|
71
|
+
|
72
|
+
# Configure a different group and run a call within that group
|
73
|
+
SlowDown.run(:my_group, requests_per_second: 2, timeout: 1) do
|
74
|
+
# ...
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Defaults & available options
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
SlowDown.config do |c|
|
82
|
+
# The allowed number of calls per second.
|
83
|
+
c.requests_per_second = 10
|
84
|
+
|
85
|
+
# The number of seconds during which SlowDown will try and acquire the resource
|
86
|
+
# for a given call.
|
87
|
+
c.timeout = 5
|
88
|
+
|
89
|
+
# Whether to raise an error when the timeout was reached and the resource could
|
90
|
+
# not be acquired.
|
91
|
+
# Raises SlowDown::Timeout.
|
92
|
+
c.raise_on_timeout = false
|
93
|
+
|
94
|
+
# How many retries should be performed til the timeout is reached.
|
95
|
+
c.retries = 30
|
96
|
+
|
97
|
+
# The algorithm used to schedule the amount of time to wait between retries.
|
98
|
+
# Available strategies: :linear, :inverse_exponential, :fibonacci or a class
|
99
|
+
# extending SlowDown::Strategy::Base.
|
100
|
+
c.retry_strategy = :linear
|
101
|
+
|
102
|
+
# Redis can be configured either directly, by setting a Redis instance to this
|
103
|
+
# variable, or via the REDIS_URL environment variable or via the redis_url
|
104
|
+
# setting.
|
105
|
+
c.redis = nil
|
106
|
+
|
107
|
+
# Configure Redis via the instance URL.
|
108
|
+
c.redis_url = nil
|
109
|
+
|
110
|
+
# The Redis namespace to apply to all locks.
|
111
|
+
c.redis_namespace = :slow_down
|
112
|
+
|
113
|
+
# The namespace to apply to the default group.
|
114
|
+
# Individual groups will overwrite this with the group name.
|
115
|
+
c.lock_namespace = :default
|
116
|
+
|
117
|
+
# Set this to a path or file descriptor in order to log to file.
|
118
|
+
c.log_path = STDOUT
|
119
|
+
|
120
|
+
# By default, the SlowDown logger is disabled.
|
121
|
+
# Set this to Logger::DEBUG, Logger::INFO or Logger::ERROR for logging various
|
122
|
+
# runtime information.
|
123
|
+
c.log_level = Logger::UNKNOWN
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
### Non-blocking checks
|
128
|
+
|
129
|
+
A call to `.run` will halt until the resource is either acquired or the timeout kicks in.
|
130
|
+
In order to make a non-blocking check, you can use the `SlowDown.free?` method.
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
SlowDown.config do |c|
|
134
|
+
c.requests_per_second = 2
|
135
|
+
end
|
136
|
+
|
137
|
+
SlowDown.free? # true
|
138
|
+
SlowDown.free? # true
|
139
|
+
SlowDown.free? # false (won't wait)
|
140
|
+
sleep(1)
|
141
|
+
SlowDown.free? # true
|
142
|
+
```
|
143
|
+
|
144
|
+
The `SlowDown.free?` method also works with **groups** and **inline configuration**:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
def register_user(name, address, phone)
|
148
|
+
user.name = name
|
149
|
+
|
150
|
+
# Optional: geocode address, if we didn't exceed the request limit
|
151
|
+
if SlowDown.free?(:geocoding, requests_per_second: 5)
|
152
|
+
user.coordinates = geocode(address)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Optional: send SMS, if we didn't exceed the request limit
|
156
|
+
if SlowDown.free?(:sms, requests_per_second: 10)
|
157
|
+
send_sms(phone)
|
158
|
+
end
|
159
|
+
|
160
|
+
user.save
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
### Resetting the locks
|
165
|
+
|
166
|
+
If you ever need to reset the locks, you can do that for any group by calling:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
SlowDown.reset(:group_name)
|
170
|
+
```
|
171
|
+
|
172
|
+
### Polling strategies
|
173
|
+
|
174
|
+
When a request is placed that can't access the lock right away, **SlowDown** puts it to sleep and schedules it to wake up & try again for the amount of retries configured by the user (defaulting to 30 retries).
|
175
|
+
|
176
|
+
The spread of these *retry sessions* can be linear (default behaviour) or non-linear - in case you want to simulate different strategies:
|
177
|
+
|
178
|
+
1. **FIFO**: Inverse exponential series - set `SlowDown.config { |c| c.retry_strategy = :inverse_exponential }`
|
179
|
+
2. **LIFO**: Fibonacci series - set `SlowDown.config { |c| c.retry_strategy = :fibonacci }`
|
180
|
+
|
181
|
+
These polling strategies are just a proof of concept and their behaviour relies more on probabilities.
|
182
|
+
|
183
|
+
## Inspiration
|
184
|
+
|
185
|
+
- [Distributed locks using Redis](https://engineering.gosquared.com/distributed-locks-using-redis)
|
186
|
+
- [Redis SET Documentation](http://redis.io/commands/set)
|
187
|
+
- [mario-redis-lock](https://github.com/marioizquierdo/mario-redis-lock)
|
188
|
+
- [redlock-rb](https://github.com/antirez/redlock-rb)
|
189
|
+
|
190
|
+
## Quotes
|
191
|
+
|
192
|
+
- *It's like sleep but more classy*
|
193
|
+
- *It's like sleep but over-engineered*
|
194
|
+
- *SlowHand*
|
195
|
+
|
196
|
+
## Development
|
197
|
+
|
198
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
199
|
+
|
200
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
201
|
+
|
202
|
+
## Contributing
|
203
|
+
|
204
|
+
1. Fork it ( https://github.com/lipanski/slow_down/fork )
|
205
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
206
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
207
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
208
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "slow_down"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/slow_down.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
if %w(development test).include?(ENV["RACK_ENV"])
|
2
|
+
require "dotenv"
|
3
|
+
Dotenv.load
|
4
|
+
end
|
5
|
+
|
6
|
+
require "slow_down/version"
|
7
|
+
require "slow_down/group"
|
8
|
+
|
9
|
+
module SlowDown
|
10
|
+
module_function
|
11
|
+
|
12
|
+
Timeout = Class.new(StandardError)
|
13
|
+
ConfigError = Class.new(StandardError)
|
14
|
+
|
15
|
+
def config(group_name = :default)
|
16
|
+
group = Group.find_or_create(group_name)
|
17
|
+
|
18
|
+
group.config.tap do |c|
|
19
|
+
yield(c) if block_given?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def groups
|
24
|
+
Group.all
|
25
|
+
end
|
26
|
+
|
27
|
+
def run(*args, &block)
|
28
|
+
find_or_create_group(*args).run(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def free?(*args)
|
32
|
+
find_or_create_group(*args).free?
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset(group_name = :default)
|
36
|
+
if group = Group.find(group_name)
|
37
|
+
group.reset
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_or_create_group(*args)
|
42
|
+
if args[0].is_a?(Hash)
|
43
|
+
group_name = :default
|
44
|
+
options = args[0]
|
45
|
+
else
|
46
|
+
group_name = args[0] || :default
|
47
|
+
options = args[1] || {}
|
48
|
+
end
|
49
|
+
|
50
|
+
Group.find_or_create(group_name, options)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "redis"
|
3
|
+
require "slow_down/strategy/linear"
|
4
|
+
require "slow_down/strategy/fibonacci"
|
5
|
+
require "slow_down/strategy/inverse_exponential"
|
6
|
+
|
7
|
+
module SlowDown
|
8
|
+
class Configuration
|
9
|
+
CONCURRENCY_MULTIPLIER = 1
|
10
|
+
|
11
|
+
DEFAULTS = {
|
12
|
+
requests_per_second: 10,
|
13
|
+
timeout: 5,
|
14
|
+
raise_on_timeout: false,
|
15
|
+
retries: 30,
|
16
|
+
retry_strategy: :linear,
|
17
|
+
redis: nil,
|
18
|
+
redis_url: nil,
|
19
|
+
redis_namespace: :slow_down,
|
20
|
+
lock_namespace: :default,
|
21
|
+
concurrency: nil,
|
22
|
+
log_path: STDOUT,
|
23
|
+
log_level: Logger::UNKNOWN
|
24
|
+
}
|
25
|
+
|
26
|
+
DEFAULTS.each do |key, default_value|
|
27
|
+
define_method(key) do
|
28
|
+
@options[key] || default_value
|
29
|
+
end
|
30
|
+
|
31
|
+
define_method("#{key}=") do |value|
|
32
|
+
@options[key] = value
|
33
|
+
invalidate
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(options)
|
38
|
+
@options = DEFAULTS.merge(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def logger
|
42
|
+
@logger ||= Logger.new(log_path).tap do |l|
|
43
|
+
l.level = log_level
|
44
|
+
l.formatter = proc do |severity, time, group_name, message|
|
45
|
+
"#{time},#{severity},##{Process.pid},#{group_name}: #{message}\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def redis
|
51
|
+
@redis ||= @options[:redis] || Redis.new(url: redis_url || ENV.fetch("REDIS_URL"))
|
52
|
+
end
|
53
|
+
|
54
|
+
def concurrency
|
55
|
+
@concurrency ||= @options[:concurrency] || [1, requests_per_second.ceil * CONCURRENCY_MULTIPLIER].max
|
56
|
+
end
|
57
|
+
|
58
|
+
def locks
|
59
|
+
@locks ||= concurrency.times.map do |i|
|
60
|
+
[redis_namespace, "#{lock_namespace}_#{i}"].compact.join(":")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def miliseconds_per_request
|
65
|
+
@miliseconds_per_request ||= 1000.0 / requests_per_second
|
66
|
+
end
|
67
|
+
|
68
|
+
def miliseconds_per_request_per_lock
|
69
|
+
@miliseconds_per_request_per_lock ||= (miliseconds_per_request * concurrency).round
|
70
|
+
end
|
71
|
+
|
72
|
+
def seconds_per_retry(retry_count)
|
73
|
+
seconds_per_retry_arr[retry_count - 1]
|
74
|
+
end
|
75
|
+
|
76
|
+
def seconds_per_retry_arr
|
77
|
+
@seconds_per_retry_arr ||= begin
|
78
|
+
klass =
|
79
|
+
case retry_strategy
|
80
|
+
when :linear
|
81
|
+
Strategy::Linear
|
82
|
+
when :fibonacci
|
83
|
+
Strategy::Fibonacci
|
84
|
+
when :inverse_exponential
|
85
|
+
Strategy::InverseExponential
|
86
|
+
else
|
87
|
+
retry_strategy
|
88
|
+
end
|
89
|
+
|
90
|
+
unless klass.is_a?(Class) && klass < Strategy::Base
|
91
|
+
fail ConfigError, ":retry_strategy should be a class inheriting SlowDown::Strategy::Base"
|
92
|
+
end
|
93
|
+
|
94
|
+
klass.new(retries, timeout).normalized_series
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def invalidate
|
99
|
+
@redis = nil
|
100
|
+
@log_path = nil
|
101
|
+
@log_level = nil
|
102
|
+
@concurrency = nil
|
103
|
+
@locks = nil
|
104
|
+
@miliseconds_per_request = nil
|
105
|
+
@miliseconds_per_request_per_lock = nil
|
106
|
+
@seconds_per_retry = nil
|
107
|
+
@seconds_per_retry_arr = nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "slow_down/configuration"
|
2
|
+
|
3
|
+
module SlowDown
|
4
|
+
class Group
|
5
|
+
def self.all
|
6
|
+
@groups || {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.find(name)
|
10
|
+
all[name]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create(name, options = {})
|
14
|
+
@groups ||= {}
|
15
|
+
@groups[name] = Group.new(name, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_or_create(name, options = {})
|
19
|
+
if all[name] && !options.empty?
|
20
|
+
all[name].config.logger.error(name) { "Group #{name} has already been configured elsewhere" }
|
21
|
+
fail ConfigError, "Group #{name} has already been configured elsewhere - you may not override configurations"
|
22
|
+
end
|
23
|
+
|
24
|
+
all[name] || create(name, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.remove(group_name)
|
28
|
+
return unless group = Group.find(group_name)
|
29
|
+
|
30
|
+
group.reset
|
31
|
+
@groups.delete(group_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.remove_all
|
35
|
+
all.each_value(&:remove)
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :name, :config
|
39
|
+
|
40
|
+
def initialize(name, options = {})
|
41
|
+
@name = name
|
42
|
+
@config = Configuration.new({ lock_namespace: name }.merge(options))
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
expires_at, iteration = Time.now + config.timeout, 0
|
47
|
+
config.logger.debug(name) { "Run attempt initiatied, times out at #{expires_at}" }
|
48
|
+
|
49
|
+
begin
|
50
|
+
return yield if free?
|
51
|
+
wait(iteration += 1)
|
52
|
+
end until Time.now > expires_at
|
53
|
+
|
54
|
+
config.logger.debug(name) { "Run attempt timed out" }
|
55
|
+
if config.raise_on_timeout
|
56
|
+
config.logger.error(name) { "Timeout error raised" }
|
57
|
+
raise Timeout
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def free?
|
62
|
+
config.locks.each do |key|
|
63
|
+
if config.redis.set(key, 1, px: config.miliseconds_per_request_per_lock, nx: true)
|
64
|
+
config.logger.info(name) { "Lock #{key} was acquired for #{config.miliseconds_per_request_per_lock}ms" }
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def reset
|
73
|
+
config.locks.each { |key| config.redis.del(key) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def remove
|
77
|
+
Group.remove(@name)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def wait(iteration)
|
83
|
+
config.logger.debug(name) { "Sleeping for #{config.seconds_per_retry(iteration) * 1000}ms" }
|
84
|
+
sleep(config.seconds_per_retry(iteration))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SlowDown
|
2
|
+
module Strategy
|
3
|
+
class Base
|
4
|
+
attr_reader :n, :max
|
5
|
+
|
6
|
+
def initialize(n, max)
|
7
|
+
@n, @max = n, max
|
8
|
+
end
|
9
|
+
|
10
|
+
def series
|
11
|
+
fail NotImplemented
|
12
|
+
end
|
13
|
+
|
14
|
+
def normalized_series
|
15
|
+
sum = series.inject(:+)
|
16
|
+
ratio = max.to_f / sum
|
17
|
+
|
18
|
+
series.map { |el| el * ratio }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/slow_down.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "slow_down/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "slow_down"
|
8
|
+
spec.version = SlowDown::VERSION
|
9
|
+
spec.authors = ["Florin Lipan"]
|
10
|
+
spec.email = ["lipanski@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A centralized Redis-based lock to help you wait on throttled resources}
|
13
|
+
spec.description = %q{A centralized Redis-based lock to help you wait on throttled resources}
|
14
|
+
spec.homepage = "https://github.com/lipanski/slow_down"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "redis"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "dotenv"
|
27
|
+
spec.add_development_dependency "minitest"
|
28
|
+
spec.add_development_dependency "minitest-reporters"
|
29
|
+
spec.add_development_dependency "minitest-stub-const"
|
30
|
+
spec.add_development_dependency "m"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slow_down
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Florin Lipan
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotenv
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-reporters
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest-stub-const
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: m
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: A centralized Redis-based lock to help you wait on throttled resources
|
126
|
+
email:
|
127
|
+
- lipanski@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".env.example"
|
133
|
+
- ".gitignore"
|
134
|
+
- ".travis.yml"
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE.txt
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- bin/console
|
140
|
+
- bin/setup
|
141
|
+
- lib/slow_down.rb
|
142
|
+
- lib/slow_down/configuration.rb
|
143
|
+
- lib/slow_down/group.rb
|
144
|
+
- lib/slow_down/strategy/base.rb
|
145
|
+
- lib/slow_down/strategy/fibonacci.rb
|
146
|
+
- lib/slow_down/strategy/inverse_exponential.rb
|
147
|
+
- lib/slow_down/strategy/linear.rb
|
148
|
+
- lib/slow_down/version.rb
|
149
|
+
- slow_down.gemspec
|
150
|
+
homepage: https://github.com/lipanski/slow_down
|
151
|
+
licenses:
|
152
|
+
- MIT
|
153
|
+
metadata: {}
|
154
|
+
post_install_message:
|
155
|
+
rdoc_options: []
|
156
|
+
require_paths:
|
157
|
+
- lib
|
158
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
requirements: []
|
169
|
+
rubyforge_project:
|
170
|
+
rubygems_version: 2.4.5
|
171
|
+
signing_key:
|
172
|
+
specification_version: 4
|
173
|
+
summary: A centralized Redis-based lock to help you wait on throttled resources
|
174
|
+
test_files: []
|
175
|
+
has_rdoc:
|