resque-waiting-room 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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: