resque-waiting-room 0.1.3

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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p0
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format Fuubar
3
+ --drb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in resque-waiting-room.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ guard 'rspec', :version => 2, :cli => "--drb --format Fuubar --color" do
2
+ watch('lib/resque/plugins/job.rb')
3
+ watch('lib/resque/plugins/waiting_room.rb')
4
+ watch('spec/resque/plugins/job_spec.rb')
5
+ watch('spec/resque/plugins/waiting_room_spec.rb')
6
+ watch('spec/spec_helper.rb')
7
+ end
8
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Julien Blanchard
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ Software), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,28 @@
1
+ Resque Waiting Room
2
+ ===================
3
+
4
+ A [Resque][rq] plugin. Requires Resque 1.19.0.
5
+
6
+ If you want to limit the number of performs of a job for a given period, extend it
7
+ with this module.
8
+
9
+ For example:
10
+
11
+ require 'resque/plugins/waiting_room'
12
+
13
+ class UpdateDataFromExternalAPI
14
+ extend Resque::Plugins::WaitingRoom
15
+
16
+ # This job can be performed 10 times every 30 seconds
17
+ can_be_performed times: 10, period: 30
18
+
19
+ def self.perform(repo_id)
20
+ blah
21
+ end
22
+ end
23
+
24
+ If 10 UpdateDataFromExternalAPI jobs have been performed in less than
25
+ 30 seconds, next job will be placed placed in the waiting_room queue
26
+ and processed when possible.
27
+
28
+ [rq]: http://github.com/julienXX/resque
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -0,0 +1,23 @@
1
+ module Resque
2
+ class Job
3
+ class <<self
4
+ alias_method :origin_reserve, :reserve
5
+
6
+ def reserve(queue)
7
+ if queue =~ /^waiting_room/ && Resque.size(queue) > 0
8
+ payload = Resque.pop(queue)
9
+ if payload
10
+ klass = constantize(payload['class'])
11
+ repushed_in_waiting_room = klass.repush(*payload['args'])
12
+
13
+ return new(queue, payload) unless repushed_in_waiting_room
14
+ end
15
+ return nil
16
+ else
17
+ origin_reserve(queue)
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module WaitingRoom
4
+ VERSION="0.1.3"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ module Resque
2
+ module Plugins
3
+ module WaitingRoom
4
+ class MissingParams < RuntimeError; end
5
+
6
+ def can_be_performed(params)
7
+ raise MissingParams unless params.is_a?(Hash) && params.keys.sort == [:period, :times]
8
+
9
+ @period ||= params[:period]
10
+ @max_performs ||= params[:times].to_i
11
+ end
12
+
13
+ def waiting_room_redis_key
14
+ [self.to_s, "remaining_performs"].compact.join(":")
15
+ end
16
+
17
+ def before_perform_waiting_room(*args)
18
+ key = waiting_room_redis_key
19
+
20
+ if has_remaining_performs_key?(key)
21
+ performs_left = Resque.redis.decrby(key, 1).to_i
22
+
23
+ if performs_left < 1
24
+ Resque.push 'waiting_room', :class => self.to_s, :args => args
25
+ raise Resque::Job::DontPerform
26
+ end
27
+ end
28
+ end
29
+
30
+ def has_remaining_performs_key?(key)
31
+ # Redis SETNX: sets the keys if it doesn't exist, returns true if key was created
32
+ new_key = Resque.redis.setnx(key, @max_performs - 1)
33
+ Resque.redis.expire(key, @period) if new_key
34
+
35
+ return !new_key
36
+ end
37
+
38
+ def repush(*args)
39
+ key = waiting_room_redis_key
40
+ value = Resque.redis.get(key)
41
+ no_performs_left = value && value != "" && value.to_i <= 0
42
+ Resque.push 'waiting_room', class: self.to_s, args: args if no_performs_left
43
+
44
+ return no_performs_left
45
+ end
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,4 @@
1
+ require 'resque'
2
+ require 'resque/plugins/version'
3
+ require 'resque/plugins/job'
4
+ require 'resque/plugins/waiting_room'
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib/resque/plugins", __FILE__)
3
+ require "version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "resque-waiting-room"
7
+ s.version = Resque::Plugins::WaitingRoom::VERSION
8
+ s.authors = ["Julien Blanchard"]
9
+ s.email = ["julien@sideburns.eu"]
10
+ s.homepage = "https://www.github.com/julienXX/resque-waiting-room"
11
+ s.summary = %q{Put your Resque jobs in a waiting room}
12
+ s.description = %q{Throttle your Resque jobs}
13
+
14
+ s.rubyforge_project = "resque-waiting-room"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.files += Dir.glob("lib/**/*")
20
+ s.files += Dir.glob("spec/**/*")
21
+
22
+ s.add_development_dependency 'rake', '~>0.9.2.2'
23
+ s.add_development_dependency 'resque', '~>1.19.0'
24
+ s.add_development_dependency 'rspec', '~>2.8.0'
25
+ s.add_development_dependency 'fuubar', '~>1.0.0'
26
+ s.add_development_dependency 'spork', '~>0.9.0'
27
+ s.add_development_dependency 'guard', '~>0.10.0'
28
+ s.add_development_dependency 'guard-rspec', '~>0.6.0'
29
+ s.add_development_dependency 'guard-spork', '~>0.3.0'
30
+ s.add_development_dependency 'rb-fsevent', '~>0.4.3.1'
31
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Resque::Job do
4
+ before(:each) do
5
+ Resque.redis.flushall
6
+ end
7
+
8
+ context "normal job" do
9
+ it "should trigger original reserve" do
10
+ Resque.push('normal', :class => 'DummyJob', :args => ['any args'])
11
+ Resque::Job.reserve('normal').should == Resque::Job.new('normal', {'class' => 'DummyJob', 'args' => ['any args']})
12
+ Resque::Job.reserve('waiting_room').should be_nil
13
+ end
14
+ end
15
+
16
+ context "waiting_room job" do
17
+ it "should push in the waiting_room queue when reserve from waiting_room queue" do
18
+ Resque.push('waiting_room', :class => 'DummyJob', :args => ['any args'])
19
+ Resque::Job.reserve('waiting_room').should == Resque::Job.new('waiting_room', {'class' => 'DummyJob', 'args' => ['any args']})
20
+ Resque::Job.reserve('normal').should be_nil
21
+ end
22
+
23
+ it "should push back to waiting_room queue when still restricted" do
24
+ Resque.push('waiting_room', :class => 'DummyJob', :args => ['any args'])
25
+ DummyJob.should_receive(:repush).with('any args')
26
+ Resque::Job.reserve('waiting_room').should == Resque::Job.new('waiting_room', {'class' => 'DummyJob', 'args' => ['any args']})
27
+ Resque::Job.reserve('waiting_room').should be_nil
28
+ Resque::Job.reserve('normal').should be_nil
29
+ end
30
+
31
+ it "should not repush when reserve normal queue" do
32
+ Resque.push('normal', :class => 'DummyJob', :args => ['any args'])
33
+ DummyJob.should_not_receive(:repush).with('any args')
34
+ Resque::Job.reserve('normal').should == Resque::Job.new('normal', {'class' => 'DummyJob', 'args' => ['any args']})
35
+ Resque::Job.reserve('normal').should be_nil
36
+ Resque::Job.reserve('waiting_room').should be_nil
37
+ end
38
+ end
39
+
40
+ end
41
+
@@ -0,0 +1,118 @@
1
+ require File.join(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Resque::Plugins::WaitingRoom do
4
+ before(:each) do
5
+ Resque.redis.flushall
6
+ end
7
+
8
+ it "should validate the Resque linter" do
9
+ Resque::Plugin.lint(Resque::Plugins::WaitingRoom)
10
+ end
11
+
12
+ context "can_be_performed" do
13
+ it "should raise InvalidParams" do
14
+ expect {DummyJob.can_be_performed('lol')}.should raise_error(Resque::Plugins::WaitingRoom::MissingParams)
15
+ end
16
+
17
+ it "should assign @period and @max_performs" do
18
+ DummyJob.instance_variable_get("@period").should == 30
19
+ DummyJob.instance_variable_get("@max_performs").should == 10
20
+ end
21
+ end
22
+
23
+ context "waiting_room_redis_key" do
24
+ it "should generate a redis key name based on the class" do
25
+ DummyJob.waiting_room_redis_key.should == 'DummyJob:remaining_performs'
26
+ end
27
+ end
28
+
29
+ context "before_perform_waiting_room" do
30
+ it "should call waiting_room_redis_key" do
31
+ DummyJob.should_receive(:waiting_room_redis_key).and_return('DummyJob:remaining_performs')
32
+ DummyJob.before_perform_waiting_room('args')
33
+ end
34
+
35
+ it "should call has_remaining_performs_key?" do
36
+ DummyJob.should_receive(:has_remaining_performs_key?).and_return(false)
37
+ DummyJob.before_perform_waiting_room('args')
38
+ end
39
+
40
+ it "should decrement performs" do
41
+ DummyJob.before_perform_waiting_room('args')
42
+ Resque.redis.get("DummyJob:remaining_performs").should =="9"
43
+ DummyJob.before_perform_waiting_room('args')
44
+ Resque.redis.get("DummyJob:remaining_performs").should =="8"
45
+ DummyJob.before_perform_waiting_room('args')
46
+ Resque.redis.get("DummyJob:remaining_performs").should =="7"
47
+ end
48
+
49
+ it "should prevent perform once there are no performs left" do
50
+ 9.times {DummyJob.before_perform_waiting_room('args')}
51
+ Resque.redis.get("DummyJob:remaining_performs").should =="1"
52
+ expect { DummyJob.before_perform_waiting_room('args') }.should raise_exception(Resque::Job::DontPerform)
53
+ end
54
+ end
55
+
56
+ context "has_remaining_performs_key?" do
57
+ it "should set a redis key" do
58
+ Resque.redis.should_receive(:setnx)
59
+ DummyJob.has_remaining_performs_key?(DummyJob.waiting_room_redis_key)
60
+ end
61
+
62
+ it "should expire the redis key" do
63
+ Resque.redis.should_receive(:setnx).and_return(true)
64
+ Resque.redis.should_receive(:expire)
65
+ DummyJob.has_remaining_performs_key?(DummyJob.waiting_room_redis_key)
66
+ end
67
+
68
+ it "should not re-expire the redis key if it is already created" do
69
+ Resque.redis.should_receive(:setnx).and_return(true)
70
+ Resque.redis.should_receive(:expire)
71
+ DummyJob.has_remaining_performs_key?(DummyJob.waiting_room_redis_key)
72
+ Resque.redis.should_receive(:setnx).and_return(false)
73
+ Resque.redis.should_not_receive(:expire)
74
+ DummyJob.has_remaining_performs_key?(DummyJob.waiting_room_redis_key)
75
+ end
76
+
77
+ it "should return false if the key is new" do
78
+ Resque.redis.should_receive(:setnx).and_return(true)
79
+ Resque.redis.should_receive(:expire)
80
+ DummyJob.has_remaining_performs_key?(DummyJob.waiting_room_redis_key).should == false
81
+ end
82
+
83
+ it "should return true if the key was already created" do
84
+ Resque.redis.should_receive(:setnx).and_return(false)
85
+ DummyJob.has_remaining_performs_key?(DummyJob.waiting_room_redis_key).should == true
86
+ end
87
+ end
88
+
89
+ context "repush" do
90
+ it "should call waiting_room_redis_key" do
91
+ DummyJob.should_receive(:waiting_room_redis_key).and_return('DummyJob:remaining_performs')
92
+ DummyJob.repush('args')
93
+ end
94
+
95
+ it "should get the key" do
96
+ Resque.redis.should_receive(:get).with(DummyJob.waiting_room_redis_key)
97
+ DummyJob.repush('args')
98
+ end
99
+
100
+ it "should push in the waiting_room if there are no performs left" do
101
+ Resque.redis.should_receive(:get).with(DummyJob.waiting_room_redis_key).and_return('0')
102
+ Resque.should_receive(:push).with('waiting_room', class: 'DummyJob', args: ['args']).and_return(true)
103
+ DummyJob.repush('args')
104
+ end
105
+
106
+ it "should return true if there were no performs left" do
107
+ Resque.redis.should_receive(:get).with(DummyJob.waiting_room_redis_key).and_return('0')
108
+ DummyJob.repush('args').should == true
109
+ end
110
+
111
+ it "should return false if there were performs left" do
112
+ Resque.redis.should_receive(:get).with(DummyJob.waiting_room_redis_key).and_return('1')
113
+ DummyJob.repush('args').should == false
114
+ end
115
+ end
116
+
117
+ end
118
+
@@ -0,0 +1,16 @@
1
+ require 'spork'
2
+ require 'resque'
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'resque-waiting-room'))
4
+
5
+ Spork.prefork do
6
+ spec_dir = File.dirname(__FILE__)
7
+ lib_dir = File.expand_path(File.join(spec_dir, '..', 'lib'))
8
+ $:.unshift(lib_dir)
9
+ $:.uniq!
10
+ RSpec.configure do |config|
11
+ config.mock_framework = :rspec
12
+ end
13
+
14
+ # Require ruby files in support dir.
15
+ Dir[File.expand_path('spec/support/*.rb')].each { |file| require file }
16
+ end
@@ -0,0 +1,8 @@
1
+ require File.join(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ class DummyJob
4
+ extend Resque::Plugins::WaitingRoom
5
+ can_be_performed times: 10, period: 30
6
+
7
+ def self.perform(*args); end
8
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-waiting-room
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Julien Blanchard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &70112971736560 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.2.2
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70112971736560
25
+ - !ruby/object:Gem::Dependency
26
+ name: resque
27
+ requirement: &70112971735960 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.19.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70112971735960
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70112971735380 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70112971735380
47
+ - !ruby/object:Gem::Dependency
48
+ name: fuubar
49
+ requirement: &70112971734900 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70112971734900
58
+ - !ruby/object:Gem::Dependency
59
+ name: spork
60
+ requirement: &70112971734380 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 0.9.0
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70112971734380
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: &70112971733860 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 0.10.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70112971733860
80
+ - !ruby/object:Gem::Dependency
81
+ name: guard-rspec
82
+ requirement: &70112971733260 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: 0.6.0
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70112971733260
91
+ - !ruby/object:Gem::Dependency
92
+ name: guard-spork
93
+ requirement: &70112971732640 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ~>
97
+ - !ruby/object:Gem::Version
98
+ version: 0.3.0
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *70112971732640
102
+ - !ruby/object:Gem::Dependency
103
+ name: rb-fsevent
104
+ requirement: &70112971731760 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.4.3.1
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *70112971731760
113
+ description: Throttle your Resque jobs
114
+ email:
115
+ - julien@sideburns.eu
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - .gitignore
121
+ - .rbenv-version
122
+ - .rspec
123
+ - Gemfile
124
+ - Guardfile
125
+ - LICENSE
126
+ - README.markdown
127
+ - Rakefile
128
+ - lib/resque-waiting-room.rb
129
+ - lib/resque/plugins/job.rb
130
+ - lib/resque/plugins/version.rb
131
+ - lib/resque/plugins/waiting_room.rb
132
+ - resque-waiting-room.gemspec
133
+ - spec/resque/plugins/job_spec.rb
134
+ - spec/resque/plugins/waiting_room_spec.rb
135
+ - spec/spec_helper.rb
136
+ - spec/support/dummy_job.rb
137
+ homepage: https://www.github.com/julienXX/resque-waiting-room
138
+ licenses: []
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ! '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project: resque-waiting-room
157
+ rubygems_version: 1.8.11
158
+ signing_key:
159
+ specification_version: 3
160
+ summary: Put your Resque jobs in a waiting room
161
+ test_files:
162
+ - spec/resque/plugins/job_spec.rb
163
+ - spec/resque/plugins/waiting_room_spec.rb
164
+ - spec/spec_helper.rb
165
+ - spec/support/dummy_job.rb
166
+ has_rdoc: