resque-concurrent-restriction 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module ConcurrentRestriction
4
+ VERSION = "0.5.0"
5
+ end
6
+ end
7
+ 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