resque-concurrent-restriction 0.5.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.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +39 -0
- data/Rakefile +2 -0
- data/lib/resque-concurrent-restriction.rb +16 -0
- data/lib/resque/plugins/concurrent_restriction/concurrent_restriction_job.rb +382 -0
- data/lib/resque/plugins/concurrent_restriction/resque_worker_extension.rb +84 -0
- data/lib/resque/plugins/concurrent_restriction/version.rb +7 -0
- data/resque-concurrent-restriction.gemspec +27 -0
- data/spec/concurrent_restriction_job_spec.rb +487 -0
- data/spec/redis-test.conf +312 -0
- data/spec/resque_worker_extensions_spec.rb +195 -0
- data/spec/spec.opts +8 -0
- data/spec/spec_helper.rb +153 -0
- metadata +107 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
module Resque
|
2
|
+
module Plugins
|
3
|
+
module ConcurrentRestriction
|
4
|
+
|
5
|
+
module Worker
|
6
|
+
|
7
|
+
def self.included(receiver)
|
8
|
+
receiver.class_eval do
|
9
|
+
alias reserve_without_restriction reserve
|
10
|
+
alias reserve reserve_with_restriction
|
11
|
+
|
12
|
+
alias done_working_without_restriction done_working
|
13
|
+
alias done_working done_working_with_restriction
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Wrap reserve so we can pass the job to done_working to release restriction if neccessary
|
18
|
+
def reserve_with_restriction
|
19
|
+
@job_in_progress = reserve_without_restriction
|
20
|
+
return @job_in_progress
|
21
|
+
end
|
22
|
+
|
23
|
+
# Wrap done_working so we can clear restriction locks after running.
|
24
|
+
# We do this here instead of in Job.perform to improve odds of completing successfully
|
25
|
+
# by running in the worker parent in case the child segfaults or something.
|
26
|
+
# This needs to be a instance method
|
27
|
+
def done_working_with_restriction
|
28
|
+
begin
|
29
|
+
job_class = @job_in_progress.payload_class
|
30
|
+
job_class.release_restriction(@job_in_progress) if job_class.is_a?(ConcurrentRestriction)
|
31
|
+
ensure
|
32
|
+
return done_working_without_restriction
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
module Job
|
39
|
+
|
40
|
+
def self.extended(receiver)
|
41
|
+
class << receiver
|
42
|
+
alias reserve_without_restriction reserve
|
43
|
+
alias reserve reserve_with_restriction
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Wrap reserve so we can move a job to restriction queue if it is restricted
|
48
|
+
# This needs to be a class method
|
49
|
+
def reserve_with_restriction(queue)
|
50
|
+
resque_job = reserve_without_restriction(queue)
|
51
|
+
|
52
|
+
if resque_job
|
53
|
+
# If there is a job on regular queues, then only run it if its not restricted
|
54
|
+
|
55
|
+
job_class = resque_job.payload_class
|
56
|
+
job_args = resque_job.args
|
57
|
+
|
58
|
+
# return to work on job if not a restricted job
|
59
|
+
if job_class.is_a?(ConcurrentRestriction)
|
60
|
+
# Move on to next if job is restricted
|
61
|
+
# If job is runnable, we keep the lock until done_working
|
62
|
+
resque_job = nil if job_class.stash_if_restricted(resque_job)
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
else
|
67
|
+
# if no queues have a runnable job, then try to find a
|
68
|
+
# runnable job from restriction queues
|
69
|
+
# This also acquires a restriction lock, which is released in done_working
|
70
|
+
resque_job = ConcurrentRestrictionJob.next_runnable_job(queue)
|
71
|
+
|
72
|
+
return resque_job
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return job or nil to move on to next queue if we couldn't get a job
|
76
|
+
return resque_job
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'resque/plugins/concurrent_restriction/version'
|
4
|
+
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "resque-concurrent-restriction"
|
8
|
+
s.version = Resque::Plugins::ConcurrentRestriction::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Matt Conway"]
|
11
|
+
s.email = ["matt@conwaysplace.com"]
|
12
|
+
s.homepage = "http://github.com/wr0ngway/resque-concurrent-restriction"
|
13
|
+
s.summary = %q{A resque plugin for limiting how many of a specific job can run concurrently}
|
14
|
+
s.description = %q{A resque plugin for limiting how many of a specific job can run concurrently}
|
15
|
+
|
16
|
+
s.rubyforge_project = "resque-concurrent-restriction"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_dependency("resque", '~> 1.10')
|
24
|
+
s.add_development_dependency('rspec', '~> 2.5')
|
25
|
+
s.add_development_dependency('awesome_print')
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,487 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Resque::Plugins::ConcurrentRestriction do
|
4
|
+
include PerformJob
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
Resque.redis.flushall
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
Resque.redis.lrange("failed", 0, -1).size.should == 0
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should follow the convention" do
|
15
|
+
Resque::Plugin.lint(Resque::Plugins::ConcurrentRestrictionJob)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
context "keys" do
|
20
|
+
it "should always contain the classname in tracking_key" do
|
21
|
+
ConcurrentRestrictionJob.tracking_key.should == "concurrent:tracking:ConcurrentRestrictionJob"
|
22
|
+
IdentifiedRestrictionJob.tracking_key.should == "concurrent:tracking:IdentifiedRestrictionJob"
|
23
|
+
IdentifiedRestrictionJob.tracking_key(1).should == "concurrent:tracking:IdentifiedRestrictionJob:1"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be able to get the class from tracking_key" do
|
27
|
+
ConcurrentRestrictionJob.tracking_class(ConcurrentRestrictionJob.tracking_key).should == ConcurrentRestrictionJob
|
28
|
+
IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key).should == IdentifiedRestrictionJob
|
29
|
+
IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key(1)).should == IdentifiedRestrictionJob
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context "encode/decode" do
|
35
|
+
|
36
|
+
it "should encode jobs correctly" do
|
37
|
+
job = Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
|
38
|
+
ConcurrentRestrictionJob.encode(job).should == '{"queue":"somequeue","payload":{"class":"RestrictionJob","args":[1,2,3]}}'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should decode jobs correctly" do
|
42
|
+
ConcurrentRestrictionJob.decode(nil).should == nil
|
43
|
+
|
44
|
+
job = ConcurrentRestrictionJob.decode('{"queue":"somequeue","payload":{"class":"RestrictionJob","args":[1,2,3]}}')
|
45
|
+
job.should == Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should rountrip encode/decode jobs correctly" do
|
49
|
+
job = Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
|
50
|
+
ConcurrentRestrictionJob.decode(ConcurrentRestrictionJob.encode(job)).should == job
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "locking" do
|
56
|
+
|
57
|
+
it "should only acquire one lock at a time" do
|
58
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
|
59
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should expire locks after timeout" do
|
63
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == true
|
64
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == false
|
65
|
+
sleep 3
|
66
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == true
|
67
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == false
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should release locks" do
|
71
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
|
72
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
|
73
|
+
|
74
|
+
ConcurrentRestrictionJob.release_lock("some_lock_key", Time.now.to_i + 5)
|
75
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
|
76
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should not release lock if expired" do
|
80
|
+
ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i - 5).should == true
|
81
|
+
ConcurrentRestrictionJob.release_lock("some_lock_key", Time.now.to_i - 5)
|
82
|
+
Resque.redis.get("some_lock_key").should_not be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should do a blocking lock while running atomically" do
|
86
|
+
counter = nil
|
87
|
+
|
88
|
+
t1 = Thread.new do
|
89
|
+
ConcurrentRestrictionJob.run_atomically("some_lock_key") do
|
90
|
+
sleep 0.2
|
91
|
+
counter = "first"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
sleep 0.1
|
96
|
+
t1.alive?.should == true
|
97
|
+
|
98
|
+
t2 = Thread.new do
|
99
|
+
ConcurrentRestrictionJob.run_atomically("some_lock_key") do
|
100
|
+
t1.alive?.should == false
|
101
|
+
counter = "second"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
t2.join
|
105
|
+
|
106
|
+
t1.join
|
107
|
+
counter.should == "second"
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
context "#helpers" do
|
113
|
+
|
114
|
+
it "should mark a job as runnable" do
|
115
|
+
IdentifiedRestrictionJob.runnables.should == []
|
116
|
+
IdentifiedRestrictionJob.runnables(:somequeue).should == []
|
117
|
+
IdentifiedRestrictionJob.runnables(:somequeue2).should == []
|
118
|
+
|
119
|
+
IdentifiedRestrictionJob.update_queues_available(IdentifiedRestrictionJob.tracking_key(1), :somequeue, :add)
|
120
|
+
IdentifiedRestrictionJob.update_queues_available(IdentifiedRestrictionJob.tracking_key(2), :somequeue2, :add)
|
121
|
+
|
122
|
+
IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(1), true)
|
123
|
+
IdentifiedRestrictionJob.runnables(:somequeue).should == [IdentifiedRestrictionJob.tracking_key(1)]
|
124
|
+
IdentifiedRestrictionJob.runnables(:somequeue2).should == []
|
125
|
+
IdentifiedRestrictionJob.runnables.should == [IdentifiedRestrictionJob.tracking_key(1)]
|
126
|
+
|
127
|
+
IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(2), true)
|
128
|
+
IdentifiedRestrictionJob.runnables(:somequeue).should == [IdentifiedRestrictionJob.tracking_key(1)]
|
129
|
+
IdentifiedRestrictionJob.runnables(:somequeue2).should == [IdentifiedRestrictionJob.tracking_key(2)]
|
130
|
+
IdentifiedRestrictionJob.runnables.sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)].sort
|
131
|
+
|
132
|
+
IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(1), false)
|
133
|
+
IdentifiedRestrictionJob.runnables(:somequeue).should == []
|
134
|
+
IdentifiedRestrictionJob.runnables(:somequeue2).should == [IdentifiedRestrictionJob.tracking_key(2)]
|
135
|
+
IdentifiedRestrictionJob.runnables.should == [IdentifiedRestrictionJob.tracking_key(2)]
|
136
|
+
|
137
|
+
IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(2), false)
|
138
|
+
IdentifiedRestrictionJob.runnables(:somequeue).should == []
|
139
|
+
IdentifiedRestrictionJob.runnables(:somequeue2).should == []
|
140
|
+
IdentifiedRestrictionJob.runnables.should == []
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
context "running count" do
|
146
|
+
|
147
|
+
it "should set running count" do
|
148
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
149
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 5)
|
150
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 5
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should increment running count" do
|
154
|
+
ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(1)
|
155
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
156
|
+
ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == false
|
157
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
|
158
|
+
ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == true
|
159
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 2
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should decrement running count" do
|
163
|
+
ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(1)
|
164
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 2)
|
165
|
+
ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == true
|
166
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
|
167
|
+
ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
|
168
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
169
|
+
ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
|
170
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
171
|
+
ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
|
172
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should be able to tell when restricted" do
|
176
|
+
ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(1)
|
177
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
|
178
|
+
ConcurrentRestrictionJob.restricted?(ConcurrentRestrictionJob.tracking_key).should == false
|
179
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
|
180
|
+
ConcurrentRestrictionJob.restricted?(ConcurrentRestrictionJob.tracking_key).should == true
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
context "restriction queue" do
|
187
|
+
|
188
|
+
it "should push jobs to the restriction queue" do
|
189
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
190
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
191
|
+
job3 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
|
192
|
+
job4 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [4]})
|
193
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job1)
|
194
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
|
195
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job2)
|
196
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1, job2]
|
197
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job3, :back)
|
198
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1, job2, job3]
|
199
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job4, :front)
|
200
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job4, job1, job2, job3]
|
201
|
+
should raise_exception() do
|
202
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job1, :bad)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should pop jobs from restriction queue" do
|
207
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
208
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
209
|
+
|
210
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job1)
|
211
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job2)
|
212
|
+
popped = ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
|
213
|
+
popped.should == job1
|
214
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job2]
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should add to queue availabilty on push" do
|
218
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
219
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
220
|
+
job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
|
221
|
+
|
222
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job1)
|
223
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
|
224
|
+
|
225
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job2)
|
226
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
|
227
|
+
|
228
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job3)
|
229
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should clean up queue availabilty on pop" do
|
233
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
234
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
235
|
+
job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
|
236
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job1)
|
237
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job2)
|
238
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job3)
|
239
|
+
|
240
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
|
241
|
+
|
242
|
+
ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
|
243
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
|
244
|
+
|
245
|
+
ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
|
246
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
|
247
|
+
|
248
|
+
ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
|
249
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == []
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should ensure runnables on queue when pushed" do
|
253
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
254
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
255
|
+
job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
|
256
|
+
|
257
|
+
|
258
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job1)
|
259
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job2)
|
260
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job3)
|
261
|
+
|
262
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 0)
|
263
|
+
|
264
|
+
ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
|
265
|
+
ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
|
266
|
+
ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should remove runnables for queues on pop" do
|
271
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
272
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
273
|
+
job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
|
274
|
+
|
275
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job1)
|
276
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job2)
|
277
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job3)
|
278
|
+
ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(5)
|
279
|
+
|
280
|
+
ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
|
281
|
+
ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
|
282
|
+
ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
|
283
|
+
ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
|
284
|
+
|
285
|
+
ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
|
286
|
+
ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
|
287
|
+
ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
|
288
|
+
ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
|
289
|
+
|
290
|
+
ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
|
291
|
+
ConcurrentRestrictionJob.runnables.sort.should == []
|
292
|
+
ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
|
293
|
+
ConcurrentRestrictionJob.runnables("somequeue2").sort.should == []
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
context "#stash_if_restricted" do
|
299
|
+
|
300
|
+
it "should return false and mark running for job that is not restricted" do
|
301
|
+
job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
302
|
+
ConcurrentRestrictionJob.stash_if_restricted(job).should == false
|
303
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should return true and not mark running for job that is restricted" do
|
307
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 99)
|
308
|
+
|
309
|
+
job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
310
|
+
ConcurrentRestrictionJob.stash_if_restricted(job).should == true
|
311
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 99
|
312
|
+
ConcurrentRestrictionJob.runnables.should == []
|
313
|
+
end
|
314
|
+
|
315
|
+
it "should add to queue availabilty on stash when restricted" do
|
316
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 99)
|
317
|
+
|
318
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
319
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
320
|
+
job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
|
321
|
+
|
322
|
+
ConcurrentRestrictionJob.stash_if_restricted(job1)
|
323
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
|
324
|
+
|
325
|
+
ConcurrentRestrictionJob.stash_if_restricted(job2)
|
326
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
|
327
|
+
|
328
|
+
ConcurrentRestrictionJob.stash_if_restricted(job3)
|
329
|
+
ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should map available queues to tracking key on push" do
|
333
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
|
334
|
+
|
335
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
|
336
|
+
job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
|
337
|
+
job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
|
338
|
+
|
339
|
+
ConcurrentRestrictionJob.stash_if_restricted(job1)
|
340
|
+
ConcurrentRestrictionJob.stash_if_restricted(job3)
|
341
|
+
ConcurrentRestrictionJob.stash_if_restricted(job2)
|
342
|
+
|
343
|
+
ConcurrentRestrictionJob.runnables.sort.should == []
|
344
|
+
ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
|
345
|
+
ConcurrentRestrictionJob.runnables("somequeue2").sort.should == []
|
346
|
+
|
347
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
|
348
|
+
|
349
|
+
ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
|
350
|
+
ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
|
351
|
+
ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
end
|
356
|
+
|
357
|
+
context "#next_runnable_job" do
|
358
|
+
|
359
|
+
it "should do nothing when nothing runnable" do
|
360
|
+
ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should not get a job if nothing runnable" do
|
364
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
365
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
|
366
|
+
ConcurrentRestrictionJob.stash_if_restricted(job1)
|
367
|
+
ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
|
368
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
|
369
|
+
end
|
370
|
+
|
371
|
+
it "should get a job if something runnable" do
|
372
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
373
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
|
374
|
+
ConcurrentRestrictionJob.stash_if_restricted(job1)
|
375
|
+
|
376
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
|
377
|
+
ConcurrentRestrictionJob.next_runnable_job('somequeue').should == job1
|
378
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == []
|
379
|
+
end
|
380
|
+
|
381
|
+
it "should not get a job if something runnable on other queue" do
|
382
|
+
job1 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
383
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
|
384
|
+
ConcurrentRestrictionJob.stash_if_restricted(job1)
|
385
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
|
386
|
+
|
387
|
+
ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
|
388
|
+
ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2").should == [job1]
|
389
|
+
end
|
390
|
+
|
391
|
+
it "should get a job for right class when called through ConcurrentRestrictionJob" do
|
392
|
+
job1 = Resque::Job.new("somequeue", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
|
393
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 99)
|
394
|
+
IdentifiedRestrictionJob.stash_if_restricted(job1)
|
395
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 0)
|
396
|
+
|
397
|
+
# Use ConcurrentRestrictionJob as thats what Resque::Worker::reserve extension has to use
|
398
|
+
ConcurrentRestrictionJob.next_runnable_job('somequeue').should == job1
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
context "#release_restriction" do
|
404
|
+
|
405
|
+
it "should decrement running count on release restriction" do
|
406
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
|
407
|
+
job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
408
|
+
ConcurrentRestrictionJob.release_restriction(job)
|
409
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should keep restriction above 0" do
|
413
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
|
414
|
+
job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
415
|
+
ConcurrentRestrictionJob.release_restriction(job)
|
416
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
|
421
|
+
context "#stats" do
|
422
|
+
|
423
|
+
it "should have blank info when nothing going on" do
|
424
|
+
stats = ConcurrentRestrictionJob.stats
|
425
|
+
stats[:queue_totals][:by_queue_name].should == {}
|
426
|
+
stats[:queue_totals][:by_identifier].should == {}
|
427
|
+
stats[:running_counts].should == {}
|
428
|
+
stats[:lock_count].should == 0
|
429
|
+
stats[:runnable_count].should == 0
|
430
|
+
end
|
431
|
+
|
432
|
+
it "should track queue_totals" do
|
433
|
+
job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
|
434
|
+
job2 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
|
435
|
+
job3 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
|
436
|
+
job4 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
|
437
|
+
job5 = Resque::Job.new("queue3", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
438
|
+
|
439
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job1)
|
440
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job2)
|
441
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job3)
|
442
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job4)
|
443
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job5)
|
444
|
+
|
445
|
+
stats = IdentifiedRestrictionJob.stats
|
446
|
+
stats[:queue_totals][:by_queue_name].should == {"queue1" => 2, "queue2" => 2, "queue3" => 1}
|
447
|
+
stats[:queue_totals][:by_identifier].should == {"IdentifiedRestrictionJob:1" => 2, "IdentifiedRestrictionJob:2" => 2, "ConcurrentRestrictionJob" => 1}
|
448
|
+
end
|
449
|
+
|
450
|
+
it "should track running_counts" do
|
451
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 2)
|
452
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(2), 3)
|
453
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 4)
|
454
|
+
stats = IdentifiedRestrictionJob.stats
|
455
|
+
stats[:running_counts].should == {"IdentifiedRestrictionJob:1" => 2, "IdentifiedRestrictionJob:2" => 3, "ConcurrentRestrictionJob" => 4}
|
456
|
+
end
|
457
|
+
|
458
|
+
it "should track lock_count" do
|
459
|
+
IdentifiedRestrictionJob.acquire_lock(IdentifiedRestrictionJob.lock_key(IdentifiedRestrictionJob.tracking_key(1)), Time.now.to_i)
|
460
|
+
IdentifiedRestrictionJob.acquire_lock(IdentifiedRestrictionJob.lock_key(IdentifiedRestrictionJob.tracking_key(2)), Time.now.to_i)
|
461
|
+
ConcurrentRestrictionJob.acquire_lock(ConcurrentRestrictionJob.lock_key(ConcurrentRestrictionJob.tracking_key), Time.now.to_i)
|
462
|
+
stats = IdentifiedRestrictionJob.stats
|
463
|
+
stats[:lock_count].should == 3
|
464
|
+
end
|
465
|
+
|
466
|
+
it "should track runnable_count" do
|
467
|
+
job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
468
|
+
job2 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
469
|
+
job3 = Resque::Job.new("somequeue", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
|
470
|
+
|
471
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
|
472
|
+
ConcurrentRestrictionJob.stash_if_restricted(job1)
|
473
|
+
ConcurrentRestrictionJob.stash_if_restricted(job2)
|
474
|
+
|
475
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 99)
|
476
|
+
IdentifiedRestrictionJob.stash_if_restricted(job3)
|
477
|
+
|
478
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
|
479
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 0)
|
480
|
+
|
481
|
+
stats = IdentifiedRestrictionJob.stats
|
482
|
+
stats[:runnable_count].should == 2
|
483
|
+
end
|
484
|
+
|
485
|
+
end
|
486
|
+
|
487
|
+
end
|