resque-throttler 0.1.1

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: 6cc4d7ea16ce3914cd16f8c6bdc42bb43d391d3d
4
+ data.tar.gz: 1315df0567f36ee68e14b4daf16f756552c871bf
5
+ SHA512:
6
+ metadata.gz: 03d5c6177b891b30cab5b4ebe9b5e31de08db32790aa2c9c132fcac835c55a8734a843b31720f915536142d73e6ed620e027fa08138d3e428188df7f7677c0e1
7
+ data.tar.gz: c1b0db2f23672b7e7566c64199dca70364ffc48342ed1922984c1739993a77940e79062e416c581f0ecc9f24d4913287045baea7f26f384097f3ab408751fc61
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sunstone.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ resque-throttler (0.1.0)
5
+ resque (~> 1.25)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (4.1.4)
11
+ i18n (~> 0.6, >= 0.6.9)
12
+ json (~> 1.7, >= 1.7.7)
13
+ minitest (~> 5.1)
14
+ thread_safe (~> 0.1)
15
+ tzinfo (~> 1.1)
16
+ ansi (1.4.3)
17
+ builder (3.2.2)
18
+ i18n (0.6.11)
19
+ json (1.8.1)
20
+ metaclass (0.0.4)
21
+ minitest (5.4.0)
22
+ minitest-reporters (1.0.4)
23
+ ansi
24
+ builder
25
+ minitest (>= 5.0)
26
+ ruby-progressbar
27
+ mocha (1.1.0)
28
+ metaclass (~> 0.0.1)
29
+ mono_logger (1.1.0)
30
+ multi_json (1.10.1)
31
+ rack (1.5.2)
32
+ rack-protection (1.5.3)
33
+ rack
34
+ rake (10.3.2)
35
+ redis (3.0.7)
36
+ redis-namespace (1.5.0)
37
+ redis (~> 3.0, >= 3.0.4)
38
+ resque (1.25.2)
39
+ mono_logger (~> 1.0)
40
+ multi_json (~> 1.0)
41
+ redis-namespace (~> 1.3)
42
+ sinatra (>= 0.9.2)
43
+ vegas (~> 0.1.2)
44
+ ruby-progressbar (1.5.1)
45
+ sinatra (1.4.5)
46
+ rack (~> 1.4)
47
+ rack-protection (~> 1.4)
48
+ tilt (~> 1.3, >= 1.3.4)
49
+ thread_safe (0.3.4)
50
+ tilt (1.4.1)
51
+ tzinfo (1.2.1)
52
+ thread_safe (~> 0.1)
53
+ vegas (0.1.11)
54
+ rack (>= 1.0.0)
55
+
56
+ PLATFORMS
57
+ ruby
58
+
59
+ DEPENDENCIES
60
+ activesupport
61
+ bundler
62
+ minitest
63
+ minitest-reporters
64
+ mocha
65
+ rake
66
+ resque-throttler!
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jon Bracy
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ Resque Throttler
2
+ ================
3
+
4
+ Resque Throttler allows you to throttle the rate at which jobs are performed
5
+ on a specific queue.
6
+
7
+
8
+ Installation
9
+ ============
10
+
11
+ ```ruby
12
+ require 'resque/throttler'
13
+ ```
14
+
15
+ Or in a Gemfile:
16
+
17
+ ```ruby
18
+ require 'resque-throttler', :require => 'resque/throttler'
19
+ ```
20
+
21
+ Usage
22
+ =====
23
+
24
+ ```ruby
25
+ require 'resque'
26
+ require 'resque/throttler'
27
+
28
+ # Rate limit at 10 jobs from `my_queue` per minute
29
+ Resque.rate_limit(:my_queue, :at => 10, :per => 60)
30
+ ```
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require 'bundler/setup'
2
+ require "bundler/gem_tasks"
3
+ Bundler.require(:development)
4
+ require 'rake/testtask'
5
+ require 'rdoc/task'
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << 'lib' << 'test'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ #t.warning = true
11
+ #t.verbose = true
12
+ end
13
+
14
+ Rake::RDocTask.new do |rd|
15
+ rd.main = 'README.md'
16
+ rd.title = 'Sunstone Documentation'
17
+ rd.rdoc_dir = 'doc'
18
+
19
+ rd.options << '-f' << 'sdoc'
20
+ rd.options << '-T' << '42floors'
21
+ rd.options << '-g' # Generate github links
22
+
23
+ rd.rdoc_files.include('README.rdoc')
24
+ rd.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ desc "Run tests"
28
+ task :default => :test
29
+
30
+ namespace :pages do
31
+ #TODO: https://github.com/defunkt/sdoc-helpers/blob/master/lib/sdoc_helpers/pages.rb
32
+ end
@@ -0,0 +1,86 @@
1
+ require 'resque'
2
+ require 'securerandom'
3
+
4
+ module Resque::Plugins
5
+ module Throttler
6
+ extend self
7
+
8
+ def self.extended(other)
9
+ other.instance_variable_set(:@rate_limits, {})
10
+ end
11
+
12
+ def pop(queue)
13
+ if queue_at_or_over_rate_limit?(queue)
14
+ gc_rate_limit_data_for_queue(queue)
15
+ nil
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def rate_limit(queue, options={})
22
+ if options.keys.sort != [:at, :per]
23
+ raise ArgumentError.new("Mising either :at or :per in options")
24
+ end
25
+
26
+ @rate_limits[queue.to_s] = options
27
+ end
28
+
29
+ def rate_limit_for(queue)
30
+ @rate_limits[queue.to_s]
31
+ end
32
+
33
+ def queue_rate_limited?(queue)
34
+ @rate_limits[queue.to_s]
35
+ end
36
+
37
+ def queue_at_or_over_rate_limit?(queue)
38
+ if queue_rate_limited?(queue)
39
+ redis.scard("throttler:#{queue}_uuids") >= rate_limit_for(queue)[:at]
40
+ else
41
+ false
42
+ end
43
+ end
44
+
45
+ def gc_rate_limit_data_for_queue(queue)
46
+ return unless queue_rate_limited?(queue)
47
+
48
+ limit = rate_limit_for(queue)
49
+ queue_key = "throttler:#{queue}_uuids"
50
+ uuids = redis.smembers(queue_key)
51
+
52
+ uuids.each do |uuid|
53
+ job_ended_at = redis.hmget("throttler:jobs:#{uuid}", "ended_at")[0]
54
+ if job_ended_at && Time.at(job_ended_at.to_i) < Time.now - limit[:per]
55
+ redis.srem(queue_key, uuid)
56
+ redis.del("throttler:jobs:#{uuid}")
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+
64
+ Resque.extend(Resque::Plugins::Throttler)
65
+
66
+ class Resque::Job
67
+
68
+ def perform_with_throttler
69
+ if Resque.queue_rate_limited?(self.queue)
70
+ uuid = SecureRandom.uuid
71
+ begin
72
+ # TODO this needs to be wrapped in a transcation
73
+ redis.hmset("throttler:jobs:#{uuid}", "started_at", Time.now.to_i)
74
+ redis.sadd("throttler:#{queue}_uuids", uuid)
75
+ perform_without_throttler
76
+ ensure
77
+ redis.hmset("throttler:jobs:#{uuid}", "ended_at", Time.now.to_i)
78
+ end
79
+ else
80
+ perform_without_throttler
81
+ end
82
+ end
83
+ alias_method :perform_without_throttler, :perform
84
+ alias_method :perform, :perform_with_throttler
85
+
86
+ end
@@ -0,0 +1,31 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "resque-throttler"
3
+ s.version = '0.1.1'
4
+ s.licenses = ['MIT']
5
+ s.authors = ["Jon Bracy"]
6
+ s.email = ["jonbracy@gmail.com"]
7
+ s.homepage = "https://github.com/malomalo/resque-throttler"
8
+ s.summary = %q{Rate limit Resque Jobs}
9
+ s.description = %q{Rate limit how many times a job can be run from a queue}
10
+
11
+ s.files = `git ls-files`.split("\n")
12
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
14
+ s.extensions = []
15
+ s.require_paths = ["lib"]
16
+ #s.extra_rdoc_files = ["LICENSE", "README.md"]
17
+
18
+ # Developoment
19
+ s.add_development_dependency 'rake'
20
+ #s.add_development_dependency 'rdoc'
21
+ #s.add_development_dependency 'sdoc'
22
+ s.add_development_dependency 'bundler'
23
+ s.add_development_dependency 'activesupport'
24
+ s.add_development_dependency 'minitest'
25
+ s.add_development_dependency 'minitest-reporters'
26
+ s.add_development_dependency 'mocha'
27
+ #s.add_development_dependency 'sdoc-templates-42floors'
28
+
29
+ # Runtime
30
+ s.add_runtime_dependency 'resque', '~> 1.25'
31
+ end
@@ -0,0 +1,77 @@
1
+ require 'test_helper'
2
+
3
+ class MyJob
4
+ def self.perform
5
+ end
6
+ end
7
+
8
+ class MyErrorJob
9
+ def self.perform
10
+ raise ArgumentError
11
+ end
12
+ end
13
+
14
+ class Resque::JobTest < Minitest::Test
15
+
16
+ def setup
17
+ Resque.instance_variable_set(:@rate_limits, {})
18
+ end
19
+
20
+ test "Resque::Job::perform on unthrottled job" do
21
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
22
+
23
+ job = Resque::Job.new(:other_queue, {
24
+ 'class' => 'MyJob',
25
+ 'args' => []
26
+ })
27
+
28
+ travel_to Time.now do
29
+ SecureRandom.expects(:uuid).returns("jobuuid").never
30
+ Resque.redis.expects(:hmset).with("throttler:jobs:jobuuid", "started_at", Time.now.to_i).never
31
+ Resque.redis.expects(:sadd).with("throttler:myqueue_uuids", "jobuuid").never
32
+ Resque.redis.expects(:hmset).with("throttler:jobs:jobuuid", "ended_at", Time.now.to_i).never
33
+
34
+ job.perform
35
+ end
36
+ end
37
+
38
+ test "Resque::Job::perform on throttled job" do
39
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
40
+
41
+ job = Resque::Job.new(:myqueue, {
42
+ 'class' => 'MyJob',
43
+ 'args' => []
44
+ })
45
+
46
+ travel_to Time.now do
47
+ SecureRandom.expects(:uuid).returns("jobuuid")
48
+ Resque.redis.expects(:hmset).with("throttler:jobs:jobuuid", "started_at", Time.now.to_i).once
49
+ Resque.redis.expects(:sadd).with("throttler:myqueue_uuids", "jobuuid").once
50
+ Resque.redis.expects(:hmset).with("throttler:jobs:jobuuid", "ended_at", Time.now.to_i).once
51
+
52
+ job.perform
53
+ end
54
+ end
55
+
56
+ test "Resque::Job::perform on throttled job with job that throws error" do
57
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
58
+
59
+ job = Resque::Job.new('myqueue', {
60
+ 'class' => 'MyErrorJob',
61
+ 'args' => []
62
+ })
63
+
64
+ travel_to Time.now do
65
+ SecureRandom.expects(:uuid).returns("jobuuid")
66
+ Resque.redis.expects(:hmset).with("throttler:jobs:jobuuid", "started_at", Time.now.to_i).once
67
+ Resque.redis.expects(:sadd).with("throttler:myqueue_uuids", "jobuuid").once
68
+ Resque.redis.expects(:hmset).with("throttler:jobs:jobuuid", "ended_at", Time.now.to_i).once
69
+
70
+ assert_raises(ArgumentError) {
71
+ job.perform
72
+ }
73
+ end
74
+ end
75
+
76
+ end
77
+
@@ -0,0 +1,78 @@
1
+ require 'test_helper'
2
+
3
+ class ResqueTest < Minitest::Test
4
+
5
+ def setup
6
+ Resque.instance_variable_set(:@rate_limits, {})
7
+ end
8
+
9
+ test "Resque::rate_limit" do
10
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
11
+
12
+ assert_equal Resque.instance_variable_get(:@rate_limits), {
13
+ 'myqueue' => {:at => 10, :per => 1}
14
+ }
15
+ end
16
+
17
+ test "Resque::queue_rate_limited?" do
18
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
19
+
20
+ assert Resque.queue_rate_limited?(:myqueue)
21
+ assert Resque.queue_rate_limited?("myqueue")
22
+ end
23
+
24
+ test "Resque::queue_at_or_over_rate_limit?" do
25
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
26
+
27
+ Resque.redis.expects(:scard).with("throttler:myqueue_uuids").returns(5).twice
28
+ assert !Resque.queue_at_or_over_rate_limit?(:myqueue)
29
+ assert !Resque.queue_at_or_over_rate_limit?("myqueue")
30
+
31
+ Resque.redis.expects(:scard).with("throttler:myqueue_uuids").returns(10).twice
32
+ assert Resque.queue_at_or_over_rate_limit?(:myqueue)
33
+ assert Resque.queue_at_or_over_rate_limit?("myqueue")
34
+ end
35
+
36
+ test "Resque::pop pops on unthrottled queues" do
37
+ Resque.redis.expects(:lpop).returns(nil)
38
+
39
+ Resque.pop('myqueue')
40
+ end
41
+
42
+ test "Resque::pop skips over queues that are at or over their limit" do
43
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
44
+ Resque.expects(:queue_at_or_over_rate_limit?).with("myqueue").returns(true)
45
+ Resque.redis.expects(:lpop).never
46
+
47
+ Resque.pop('myqueue')
48
+ end
49
+
50
+
51
+ test "Resque::pop gc's the limit data after skipping over a throttled queue" do
52
+ Resque.rate_limit(:myqueue, :at => 10, :per => 1)
53
+ Resque.expects(:queue_at_or_over_rate_limit?).with("myqueue").returns(true)
54
+ Resque.expects(:gc_rate_limit_data_for_queue).with("myqueue").once
55
+
56
+ Resque.pop('myqueue')
57
+ end
58
+
59
+ test "Resque::gc_rate_limit_data_for_queue" do
60
+ Resque.rate_limit(:myqueue, :at => 10, :per => 5)
61
+ Resque.redis.expects(:smembers).with("throttler:myqueue_uuids").returns(["1","2","3"]).once
62
+ Resque.redis.expects(:srem).with("throttler:myqueue_uuids", "1").once
63
+ Resque.redis.expects(:del).with("throttler:jobs:1").once
64
+
65
+ travel_to Time.now do
66
+ Resque.redis.expects(:hmget).with("throttler:jobs:1", "ended_at").returns([(Time.now - 10).to_i])
67
+ Resque.redis.expects(:hmget).with("throttler:jobs:2", "ended_at").returns([(Time.now - 3).to_i])
68
+ Resque.redis.expects(:hmget).with("throttler:jobs:3", "ended_at").returns([nil])
69
+
70
+ Resque.gc_rate_limit_data_for_queue('myqueue')
71
+ end
72
+ end
73
+
74
+ test "Resque::gc_rate_limit_data_for_queue for unthrottled queue" do
75
+ Resque.gc_rate_limit_data_for_queue('myqueue')
76
+ end
77
+
78
+ end
@@ -0,0 +1,54 @@
1
+ # To make testing/debugging easier, test within this source tree versus an
2
+ # installed gem
3
+ dir = File.dirname(__FILE__)
4
+ root = File.expand_path(File.join(dir, '..'))
5
+ lib = File.expand_path(File.join(root, 'lib'))
6
+
7
+ $LOAD_PATH << lib
8
+
9
+ require 'resque'
10
+ require 'resque/throttler'
11
+ require "minitest/autorun"
12
+ require 'minitest/unit'
13
+ require 'minitest/reporters'
14
+ require "mocha"
15
+ require "mocha/mini_test"
16
+ require 'active_support/testing/time_helpers'
17
+
18
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
19
+
20
+ # File 'lib/active_support/testing/declarative.rb', somewhere in rails....
21
+ class Minitest::Test
22
+
23
+ include ActiveSupport::Testing::TimeHelpers
24
+
25
+ def self.test(name, &block)
26
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
27
+ defined = instance_method(test_name) rescue false
28
+ raise "#{test_name} is already defined in #{self}" if defined
29
+ if block_given?
30
+ define_method(test_name, &block)
31
+ else
32
+ define_method(test_name) do
33
+ flunk "No implementation provided for #{name}"
34
+ end
35
+ end
36
+ end
37
+
38
+ # test/unit backwards compatibility methods
39
+ alias :assert_raise :assert_raises
40
+ alias :assert_not_empty :refute_empty
41
+ alias :assert_not_equal :refute_equal
42
+ alias :assert_not_in_delta :refute_in_delta
43
+ alias :assert_not_in_epsilon :refute_in_epsilon
44
+ alias :assert_not_includes :refute_includes
45
+ alias :assert_not_instance_of :refute_instance_of
46
+ alias :assert_not_kind_of :refute_kind_of
47
+ alias :assert_no_match :refute_match
48
+ alias :assert_not_nil :refute_nil
49
+ alias :assert_not_operator :refute_operator
50
+ alias :assert_not_predicate :refute_predicate
51
+ alias :assert_not_respond_to :refute_respond_to
52
+ alias :assert_not_same :refute_same
53
+
54
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-throttler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Jon Bracy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
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: activesupport
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: minitest
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-reporters
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: mocha
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: resque
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.25'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.25'
111
+ description: Rate limit how many times a job can be run from a queue
112
+ email:
113
+ - jonbracy@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - Gemfile
120
+ - Gemfile.lock
121
+ - LICENSE
122
+ - README.md
123
+ - Rakefile
124
+ - lib/resque/throttler.rb
125
+ - resque-throttler.gemspec
126
+ - test/resque/job_test.rb
127
+ - test/resque_test.rb
128
+ - test/test_helper.rb
129
+ homepage: https://github.com/malomalo/resque-throttler
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 2.2.2
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Rate limit Resque Jobs
153
+ test_files:
154
+ - test/resque/job_test.rb
155
+ - test/resque_test.rb
156
+ - test/test_helper.rb
157
+ has_rdoc: