slow_down 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ .env
12
+ .ruby-version
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in slow_down.gemspec
4
+ gemspec
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
+ [![Build Status](https://travis-ci.org/lipanski/slow-down.svg?branch=master)](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
@@ -0,0 +1,9 @@
1
+ require "rake"
2
+ require "rake/testtask"
3
+ require "bundler/gem_tasks"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = Dir.glob("test/**/test_*.rb")
7
+ end
8
+
9
+ task(default: :test)
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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
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
@@ -0,0 +1,13 @@
1
+ require "slow_down/strategy/base"
2
+
3
+ module SlowDown
4
+ module Strategy
5
+ class Fibonacci < Base
6
+ def series
7
+ (n - 2).times.each_with_object([1, 2]) do |_, arr|
8
+ arr << arr[-2] + arr[-1]
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require "slow_down/strategy/base"
2
+
3
+ module SlowDown
4
+ module Strategy
5
+ class InverseExponential < Base
6
+ def series
7
+ n.times.map do |i|
8
+ 1 - Math::E ** (i + 1)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ require "slow_down/strategy/base"
2
+
3
+ module SlowDown
4
+ module Strategy
5
+ class Linear < Base
6
+ def series
7
+ n.times.map { 1 }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module SlowDown
2
+ VERSION = "0.2.0"
3
+ 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: