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.
@@ -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