resque-concurrent-restriction 0.5.9 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bbda1244cff78d5060249dffb53d58d9a8a84c10
4
+ data.tar.gz: 41cd25689487c7f2cc4fcba41491b9e146806606
5
+ SHA512:
6
+ metadata.gz: 5a8aad211b3ee98acbe8cf55002ff7d8af401857fbf2f6af885d93547e529eb684c3ad968cefab487c78c859052c68938bc49d423fedac8e41806c1a600a14a4
7
+ data.tar.gz: 42111765e48f618fb806f386bedd51f112a267ec0855f52578b3ac7dd0a906fefae66c7ad320b9c4e3a3f9aee1b37fee6c52219925e93474b570ea36acb8cc1e
data/.travis.yml CHANGED
@@ -8,3 +8,5 @@ rvm:
8
8
  # - rbx-18mode
9
9
  # - rbx-19mode
10
10
  script: bundle exec rspec spec
11
+ services:
12
+ - redis-server
data/README.md CHANGED
@@ -3,7 +3,7 @@ resque-concurrent-restriction
3
3
 
4
4
  Resque Concurrent Restriction is a plugin for the [Resque][0] queueing system (http://github.com/defunkt/resque). It allows one to specify how many of the given job can run concurrently.
5
5
 
6
- Resque Concurrent Restriction requires Resque 1.10 and redis 2.2
6
+ Resque Concurrent Restriction requires Resque 1.25 and redis 2.2
7
7
 
8
8
  [![Build Status](https://secure.travis-ci.org/wr0ngway/resque-concurrent-restriction.png)](http://travis-ci.org/wr0ngway/resque-concurrent-restriction)
9
9
 
@@ -18,6 +18,10 @@
18
18
  module Resque
19
19
  module Plugins
20
20
  module ConcurrentRestriction
21
+ # Warning: The helpers module will be gone in Resque 2.x
22
+ # Resque::Helpers removed from Resque in 1.25, see:
23
+ # https://github.com/resque/resque/issues/1150#issuecomment-27942972
24
+ include Resque::Helpers
21
25
 
22
26
  # Allows configuring via class accessors
23
27
  class << self
@@ -130,7 +134,7 @@ module Resque
130
134
  end
131
135
 
132
136
  def tracking_class(tracking_key)
133
- Resque.constantize(tracking_key.split(".")[2])
137
+ constantize(tracking_key.split(".")[2])
134
138
  end
135
139
 
136
140
  # The key to the redis set where we keep a list of runnable tracking_keys
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module ConcurrentRestriction
4
- VERSION = "0.5.9"
4
+ VERSION = "0.6.0"
5
5
  end
6
6
  end
7
7
  end
@@ -20,13 +20,11 @@ Gem::Specification.new do |s|
20
20
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
21
  s.require_paths = ["lib"]
22
22
 
23
- s.add_dependency("resque", '~> 1.10')
23
+ s.add_dependency("resque", '~> 1.25')
24
24
 
25
- s.add_development_dependency('json')
26
25
  s.add_development_dependency('rspec', '~> 2.5')
27
26
  s.add_development_dependency('awesome_print')
28
27
 
29
28
  # Needed for testing newer resque on ruby 1.8.7
30
29
  s.add_development_dependency('json')
31
-
32
30
  end
@@ -1,700 +1,698 @@
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
- Resque.redis.get("stat:failed").to_i.should == 0
13
- end
14
-
15
- it "should follow the convention" do
16
- Resque::Plugin.lint(Resque::Plugins::ConcurrentRestrictionJob)
17
- end
18
-
19
- context "settings" do
20
-
21
- it "should allow setting/getting global config for lock_timeout" do
22
- Resque::Plugins::ConcurrentRestriction.lock_timeout.should == 60
23
- Resque::Plugins::ConcurrentRestriction.configure do |config|
24
- config.lock_timeout = 61
25
- end
26
- Resque::Plugins::ConcurrentRestriction.lock_timeout.should == 61
27
- Resque::Plugins::ConcurrentRestriction.lock_timeout = 60
28
- Resque::Plugins::ConcurrentRestriction.lock_timeout.should == 60
29
- end
30
-
31
- it "should allow setting/getting global config for reserve_queued_job_attempts" do
32
- Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts.should == 1
33
- Resque::Plugins::ConcurrentRestriction.configure do |config|
34
- config.reserve_queued_job_attempts = 5
35
- end
36
- Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts.should == 5
37
- Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts = 3
38
- Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts.should == 3
39
- end
40
-
41
- it "should allow setting/getting global config for restricted_before_queued" do
42
- Resque::Plugins::ConcurrentRestriction.restricted_before_queued.should == false
43
- Resque::Plugins::ConcurrentRestriction.configure do |config|
44
- config.restricted_before_queued = true
45
- end
46
- Resque::Plugins::ConcurrentRestriction.restricted_before_queued.should == true
47
- Resque::Plugins::ConcurrentRestriction.restricted_before_queued = false
48
- Resque::Plugins::ConcurrentRestriction.restricted_before_queued.should == false
49
- end
50
-
51
- end
52
-
53
- context "keys" do
54
- it "should always contain the classname in tracking_key" do
55
- ConcurrentRestrictionJob.tracking_key.should == "concurrent.tracking.ConcurrentRestrictionJob"
56
- IdentifiedRestrictionJob.tracking_key.should == "concurrent.tracking.IdentifiedRestrictionJob"
57
- IdentifiedRestrictionJob.tracking_key(1).should == "concurrent.tracking.IdentifiedRestrictionJob.1"
58
- Jobs::NestedRestrictionJob.tracking_key.should == "concurrent.tracking.Jobs::NestedRestrictionJob"
59
- end
60
-
61
- it "should be able to get the class from tracking_key" do
62
- ConcurrentRestrictionJob.tracking_class(ConcurrentRestrictionJob.tracking_key).should == ConcurrentRestrictionJob
63
- IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key).should == IdentifiedRestrictionJob
64
- IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key(1)).should == IdentifiedRestrictionJob
65
- Jobs::NestedRestrictionJob.tracking_class(Jobs::NestedRestrictionJob.tracking_key).should == Jobs::NestedRestrictionJob
66
- end
67
-
68
- end
69
-
70
- context "encode/decode" do
71
-
72
- it "should encode jobs correctly" do
73
- job = Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
74
- JSON.parse(ConcurrentRestrictionJob.encode(job)).should == JSON.parse('{"queue":"somequeue","payload":{"class":"RestrictionJob","args":[1,2,3]}}')
75
- end
76
-
77
- it "should decode jobs correctly" do
78
- ConcurrentRestrictionJob.decode(nil).should == nil
79
-
80
- job = ConcurrentRestrictionJob.decode('{"queue":"somequeue","payload":{"class":"RestrictionJob","args":[1,2,3]}}')
81
- job.should == Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
82
- end
83
-
84
- it "should rountrip encode/decode jobs correctly" do
85
- job = Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
86
- ConcurrentRestrictionJob.decode(ConcurrentRestrictionJob.encode(job)).should == job
87
- end
88
-
89
- end
90
-
91
- context "locking" do
92
-
93
- it "should only acquire one lock at a time" do
94
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
95
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
96
- end
97
-
98
- it "should expire locks after timeout" do
99
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == true
100
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == false
101
- sleep 3
102
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == true
103
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == false
104
- end
105
-
106
- it "should release locks" do
107
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
108
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
109
-
110
- ConcurrentRestrictionJob.release_lock("some_lock_key", Time.now.to_i + 5)
111
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
112
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
113
- end
114
-
115
- it "should not release lock if expired" do
116
- ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i - 5).should == true
117
- ConcurrentRestrictionJob.release_lock("some_lock_key", Time.now.to_i - 5)
118
- Resque.redis.get("some_lock_key").should_not be_nil
119
- end
120
-
121
- it "should do a blocking lock while running atomically" do
122
- counter = nil
123
-
124
- t1 = Thread.new do
125
- ConcurrentRestrictionJob.run_atomically("some_lock_key") do
126
- sleep 0.2
127
- counter = "first"
128
- end
129
- end
130
-
131
- sleep 0.1
132
- t1.alive?.should == true
133
-
134
- t2 = Thread.new do
135
- ConcurrentRestrictionJob.run_atomically("some_lock_key") do
136
- t1.alive?.should == false
137
- counter = "second"
138
- end
139
- end
140
- t2.join
141
-
142
- t1.join
143
- counter.should == "second"
144
- end
145
-
146
- it "should fail if can't acquire lock within tries" do
147
- did_run1 = did_run2 = false
148
-
149
- t1 = Thread.new do
150
- ConcurrentRestrictionJob.run_atomically("some_lock_key") do
151
- sleep 0.2
152
- end
153
- end
154
-
155
- sleep 0.1
156
- t1.alive?.should == true
157
-
158
- t2 = Thread.new do
159
- ConcurrentRestrictionJob.run_atomically("some_lock_key", 2) do
160
- did_run1 = true
161
- end
162
- end
163
- t2.join
164
-
165
- t3 = Thread.new do
166
- ConcurrentRestrictionJob.run_atomically("some_lock_key", 100) do
167
- did_run2 = true
168
- end
169
- end
170
- t3.join
171
-
172
- t1.join
173
- did_run1.should be_false
174
- did_run2.should be_true
175
- end
176
-
177
- end
178
-
179
- context "#helpers" do
180
-
181
- it "should mark a job as runnable" do
182
- IdentifiedRestrictionJob.runnables.should == []
183
- IdentifiedRestrictionJob.runnables(:somequeue).should == []
184
- IdentifiedRestrictionJob.runnables(:somequeue2).should == []
185
-
186
- IdentifiedRestrictionJob.update_queues_available(IdentifiedRestrictionJob.tracking_key(1), :somequeue, :add)
187
- IdentifiedRestrictionJob.update_queues_available(IdentifiedRestrictionJob.tracking_key(2), :somequeue2, :add)
188
-
189
- IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(1), true)
190
- IdentifiedRestrictionJob.runnables(:somequeue).should == [IdentifiedRestrictionJob.tracking_key(1)]
191
- IdentifiedRestrictionJob.runnables(:somequeue2).should == []
192
- IdentifiedRestrictionJob.runnables.should == [IdentifiedRestrictionJob.tracking_key(1)]
193
-
194
- IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(2), true)
195
- IdentifiedRestrictionJob.runnables(:somequeue).should == [IdentifiedRestrictionJob.tracking_key(1)]
196
- IdentifiedRestrictionJob.runnables(:somequeue2).should == [IdentifiedRestrictionJob.tracking_key(2)]
197
- IdentifiedRestrictionJob.runnables.sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)].sort
198
-
199
- IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(1), false)
200
- IdentifiedRestrictionJob.runnables(:somequeue).should == []
201
- IdentifiedRestrictionJob.runnables(:somequeue2).should == [IdentifiedRestrictionJob.tracking_key(2)]
202
- IdentifiedRestrictionJob.runnables.should == [IdentifiedRestrictionJob.tracking_key(2)]
203
-
204
- IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(2), false)
205
- IdentifiedRestrictionJob.runnables(:somequeue).should == []
206
- IdentifiedRestrictionJob.runnables(:somequeue2).should == []
207
- IdentifiedRestrictionJob.runnables.should == []
208
- end
209
-
210
- end
211
-
212
- context "running count" do
213
-
214
- it "should set running count" do
215
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
216
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 5)
217
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 5
218
- end
219
-
220
- it "should increment running count" do
221
- ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(2)
222
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
223
- ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == false
224
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
225
- ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == true
226
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 2
227
- ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == true
228
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 3
229
- end
230
-
231
- it "should decrement running count" do
232
- ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(2)
233
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 3)
234
- ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == true
235
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 2
236
- ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
237
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
238
- ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
239
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
240
- end
241
-
242
- it "should not decrement running count below 0" do
243
- ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(1)
244
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
245
- ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
246
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
247
- end
248
-
249
- it "should be able to tell when restricted" do
250
- ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(1)
251
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
252
- ConcurrentRestrictionJob.restricted?(ConcurrentRestrictionJob.tracking_key).should == false
253
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
254
- ConcurrentRestrictionJob.restricted?(ConcurrentRestrictionJob.tracking_key).should == true
255
- end
256
-
257
- end
258
-
259
- context "restriction queue" do
260
-
261
- it "should push jobs to the restriction queue" do
262
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
263
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
264
- job3 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
265
- job4 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [4]})
266
- ConcurrentRestrictionJob.push_to_restriction_queue(job1)
267
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
268
- ConcurrentRestrictionJob.push_to_restriction_queue(job2)
269
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1, job2]
270
- ConcurrentRestrictionJob.push_to_restriction_queue(job3, :back)
271
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1, job2, job3]
272
- ConcurrentRestrictionJob.push_to_restriction_queue(job4, :front)
273
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job4, job1, job2, job3]
274
- should raise_exception() do
275
- ConcurrentRestrictionJob.push_to_restriction_queue(job1, :bad)
276
- end
277
- end
278
-
279
- it "should pop jobs from restriction queue" do
280
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
281
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
282
-
283
- ConcurrentRestrictionJob.push_to_restriction_queue(job1)
284
- ConcurrentRestrictionJob.push_to_restriction_queue(job2)
285
- popped = ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
286
- popped.should == job1
287
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job2]
288
- end
289
-
290
- it "should add to queue availabilty on push" do
291
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
292
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
293
- job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
294
-
295
- ConcurrentRestrictionJob.push_to_restriction_queue(job1)
296
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
297
-
298
- ConcurrentRestrictionJob.push_to_restriction_queue(job2)
299
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
300
-
301
- ConcurrentRestrictionJob.push_to_restriction_queue(job3)
302
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
303
- end
304
-
305
- it "should clean up queue availabilty on pop" do
306
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
307
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
308
- job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
309
- ConcurrentRestrictionJob.push_to_restriction_queue(job1)
310
- ConcurrentRestrictionJob.push_to_restriction_queue(job2)
311
- ConcurrentRestrictionJob.push_to_restriction_queue(job3)
312
-
313
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
314
-
315
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
316
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
317
-
318
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
319
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
320
-
321
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
322
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == []
323
- end
324
-
325
- it "should ensure runnables on queue when pushed" do
326
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
327
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
328
- job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
329
-
330
-
331
- ConcurrentRestrictionJob.push_to_restriction_queue(job1)
332
- ConcurrentRestrictionJob.push_to_restriction_queue(job2)
333
- ConcurrentRestrictionJob.push_to_restriction_queue(job3)
334
-
335
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 0)
336
-
337
- ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
338
- ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
339
- ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
340
-
341
- end
342
-
343
- it "should remove runnables for queues on pop" do
344
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
345
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
346
- job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
347
-
348
- ConcurrentRestrictionJob.push_to_restriction_queue(job1)
349
- ConcurrentRestrictionJob.push_to_restriction_queue(job2)
350
- ConcurrentRestrictionJob.push_to_restriction_queue(job3)
351
- ConcurrentRestrictionJob.stub!(:concurrent_limit).and_return(5)
352
-
353
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
354
- ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
355
- ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
356
- ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
357
-
358
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
359
- ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
360
- ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
361
- ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
362
-
363
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
364
- ConcurrentRestrictionJob.runnables.sort.should == []
365
- ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
366
- ConcurrentRestrictionJob.runnables("somequeue2").sort.should == []
367
- end
368
-
369
- it "should track queue_counts" do
370
- ConcurrentRestrictionJob.queue_counts.should == {}
371
-
372
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
373
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
374
- job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
375
-
376
- ConcurrentRestrictionJob.push_to_restriction_queue(job1)
377
- ConcurrentRestrictionJob.push_to_restriction_queue(job2)
378
- ConcurrentRestrictionJob.push_to_restriction_queue(job3)
379
- ConcurrentRestrictionJob.queue_counts.should == {"somequeue"=>2, "somequeue2"=>1}
380
-
381
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
382
- ConcurrentRestrictionJob.queue_counts.should == {"somequeue"=>1, "somequeue2"=>1}
383
-
384
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
385
- ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
386
- ConcurrentRestrictionJob.queue_counts.should == {"somequeue"=>0, "somequeue2"=>0}
387
- end
388
- end
389
-
390
- context "#stash_if_restricted" do
391
-
392
- it "should return false and mark running for job that is not restricted" do
393
- job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
394
- ConcurrentRestrictionJob.stash_if_restricted(job).should == false
395
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
396
- end
397
-
398
- it "should return true and not mark running for job that is restricted" do
399
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 99)
400
-
401
- job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
402
- ConcurrentRestrictionJob.stash_if_restricted(job).should == true
403
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 99
404
- ConcurrentRestrictionJob.runnables.should == []
405
- end
406
-
407
- it "should add to queue availabilty on stash when restricted" do
408
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 99)
409
-
410
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
411
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
412
- job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
413
-
414
- ConcurrentRestrictionJob.stash_if_restricted(job1)
415
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
416
-
417
- ConcurrentRestrictionJob.stash_if_restricted(job2)
418
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
419
-
420
- ConcurrentRestrictionJob.stash_if_restricted(job3)
421
- ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
422
- end
423
-
424
- it "should map available queues to tracking key on push" do
425
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
426
-
427
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
428
- job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
429
- job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
430
-
431
- ConcurrentRestrictionJob.stash_if_restricted(job1)
432
- ConcurrentRestrictionJob.stash_if_restricted(job3)
433
- ConcurrentRestrictionJob.stash_if_restricted(job2)
434
-
435
- ConcurrentRestrictionJob.runnables.sort.should == []
436
- ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
437
- ConcurrentRestrictionJob.runnables("somequeue2").sort.should == []
438
-
439
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
440
-
441
- ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
442
- ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
443
- ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
444
-
445
- end
446
-
447
- it "should repush job and return true if it can't acquire a lock" do
448
- old = Resque::Plugins::ConcurrentRestriction.lock_tries
449
- begin
450
- Resque::Plugins::ConcurrentRestriction.lock_tries = 0
451
-
452
- job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
453
- ConcurrentRestrictionJob.stash_if_restricted(job).should == true
454
- Resque.peek("somequeue").should == {"class" => "ConcurrentRestrictionJob", "args" => []}
455
- ensure
456
- Resque::Plugins::ConcurrentRestriction.lock_tries = old
457
- end
458
-
459
- end
460
-
461
- end
462
-
463
- context "#next_runnable_job" do
464
-
465
- it "should do nothing when nothing runnable" do
466
- ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
467
- end
468
-
469
- it "should return nil and not pop from queue if cannot acquire lock" do
470
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
471
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
472
- ConcurrentRestrictionJob.stash_if_restricted(job1)
473
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
474
-
475
- old = Resque::Plugins::ConcurrentRestriction.lock_tries
476
- begin
477
- Resque::Plugins::ConcurrentRestriction.lock_tries = 0
478
-
479
- ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
480
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
481
- ensure
482
- Resque::Plugins::ConcurrentRestriction.lock_tries = old
483
- end
484
- end
485
-
486
- it "should not get a job if nothing runnable" do
487
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
488
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
489
- ConcurrentRestrictionJob.stash_if_restricted(job1)
490
- ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
491
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
492
- end
493
-
494
- it "should get a job if something runnable" do
495
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
496
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
497
- ConcurrentRestrictionJob.stash_if_restricted(job1)
498
-
499
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
500
- ConcurrentRestrictionJob.next_runnable_job('somequeue').should == job1
501
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == []
502
- end
503
-
504
- it "should not get a job if something runnable on other queue" do
505
- job1 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => []})
506
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
507
- ConcurrentRestrictionJob.stash_if_restricted(job1)
508
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
509
-
510
- ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
511
- ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2").should == [job1]
512
- end
513
-
514
- it "should get a job for right class when called through ConcurrentRestrictionJob" do
515
- job1 = Resque::Job.new("somequeue", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
516
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 99)
517
- IdentifiedRestrictionJob.stash_if_restricted(job1)
518
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 0)
519
-
520
- # Use ConcurrentRestrictionJob as thats what Resque::Worker::reserve extension has to use
521
- ConcurrentRestrictionJob.next_runnable_job('somequeue').should == job1
522
- end
523
-
524
- end
525
-
526
- context "#release_restriction" do
527
-
528
- it "should decrement running count on release restriction" do
529
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
530
- job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
531
- ConcurrentRestrictionJob.release_restriction(job)
532
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
533
- end
534
-
535
- it "should keep restriction above 0" do
536
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
537
- job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
538
- ConcurrentRestrictionJob.release_restriction(job)
539
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
540
- end
541
-
542
- it "should do nothing if cannot acquire lock" do
543
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
544
- job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
545
-
546
- old = Resque::Plugins::ConcurrentRestriction.lock_tries
547
- begin
548
- Resque::Plugins::ConcurrentRestriction.lock_tries = 0
549
-
550
- ConcurrentRestrictionJob.release_restriction(job)
551
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
552
- ensure
553
- Resque::Plugins::ConcurrentRestriction.lock_tries = old
554
- end
555
- end
556
-
557
- end
558
-
559
- context "#reset_restrictions" do
560
- it "should do nothing when nothing is in redis" do
561
- ConcurrentRestrictionJob.reset_restrictions.should == [0, 0]
562
- end
563
-
564
- it "should reset counts" do
565
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 5)
566
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 5)
567
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(2), 5)
568
-
569
- ConcurrentRestrictionJob.reset_restrictions.should == [3, 0]
570
- ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
571
- IdentifiedRestrictionJob.running_count(IdentifiedRestrictionJob.tracking_key(1)).should == 0
572
- IdentifiedRestrictionJob.running_count(IdentifiedRestrictionJob.tracking_key(2)).should == 0
573
- end
574
-
575
- it "should reset restriction queue runnable state" do
576
- job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
577
- job2 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
578
- job3 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
579
- job4 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
580
- job5 = Resque::Job.new("queue3", {"class" => "ConcurrentRestrictionJob", "args" => []})
581
-
582
- IdentifiedRestrictionJob.push_to_restriction_queue(job1)
583
- IdentifiedRestrictionJob.push_to_restriction_queue(job2)
584
- IdentifiedRestrictionJob.push_to_restriction_queue(job3)
585
- IdentifiedRestrictionJob.push_to_restriction_queue(job4)
586
- ConcurrentRestrictionJob.push_to_restriction_queue(job5)
587
-
588
- ConcurrentRestrictionJob.reset_restrictions.should == [0, 5]
589
- IdentifiedRestrictionJob.runnables(:queue1).sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
590
- IdentifiedRestrictionJob.runnables(:queue2).sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
591
- ConcurrentRestrictionJob.runnables(:queue3).sort.should == [ConcurrentRestrictionJob.tracking_key]
592
-
593
- IdentifiedRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key, IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
594
-
595
- ConcurrentRestrictionJob.queue_counts.should == {"queue1"=>2, "queue2"=>2, "queue3"=>1}
596
-
597
- end
598
-
599
- it "should handle a large amount of concurrent keys" do
600
- # the ruby splat operator will choke on an arguments list greater than a couple hundred thousand objects. make sure this case is handled correctly
601
- # It might be better to actually populate redis with a bunch keys but that makes the test pretty slow
602
-
603
- # we have to keep this splat limitation in mind when populating test data, too
604
- concurrent_count_keys = 20001.times.collect{ |i| ["concurrent.count.#{i}", "#{i}"] }.flatten
605
- concurrent_count_keys.each_slice(10000) do |slice|
606
- Resque.redis.mset *slice
607
- end
608
-
609
- concurrent_runnable_keys = 20001.times.collect{ |i| ["concurrent.runnable.#{i}", "#{i}"] }.flatten
610
- concurrent_runnable_keys.each_slice(10000) do |slice|
611
- Resque.redis.mset *slice
612
- end
613
-
614
- return_value = nil
615
-
616
- lambda{ return_value = ConcurrentRestrictionJob.reset_restrictions }.should_not raise_exception
617
-
618
- return_value.should == [20001, 0]
619
- end
620
-
621
- end
622
-
623
- context "#stats" do
624
-
625
- it "should have blank info when nothing going on" do
626
- stats = ConcurrentRestrictionJob.stats
627
- stats[:queues].should == {}
628
- stats[:identifiers].should == {}
629
- stats[:lock_count].should == 0
630
- stats[:runnable_count].should == 0
631
- estats = ConcurrentRestrictionJob.stats(true)
632
- estats.should == stats
633
- end
634
-
635
- it "should not track identifiers when not extended" do
636
- job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
637
- IdentifiedRestrictionJob.push_to_restriction_queue(job1)
638
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 2)
639
-
640
- stats = IdentifiedRestrictionJob.stats
641
- stats[:identifiers].should == {}
642
- end
643
-
644
- it "should track queue_totals" do
645
- job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
646
- job2 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
647
- job3 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
648
- job4 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
649
- job5 = Resque::Job.new("queue3", {"class" => "ConcurrentRestrictionJob", "args" => []})
650
-
651
- IdentifiedRestrictionJob.push_to_restriction_queue(job1)
652
- IdentifiedRestrictionJob.push_to_restriction_queue(job2)
653
- IdentifiedRestrictionJob.push_to_restriction_queue(job3)
654
- IdentifiedRestrictionJob.push_to_restriction_queue(job4)
655
- ConcurrentRestrictionJob.push_to_restriction_queue(job5)
656
-
657
- stats = IdentifiedRestrictionJob.stats(true)
658
- stats[:queues].should == {"queue1" => 2, "queue2" => 2, "queue3" => 1}
659
-
660
- stats[:identifiers].should == {"IdentifiedRestrictionJob.1"=>{"queue1"=>1, "queue2"=>1}, "IdentifiedRestrictionJob.2"=>{"queue1"=>1, "queue2"=>1}, "ConcurrentRestrictionJob"=>{"queue3"=>1}}
661
- end
662
-
663
- it "should track running_counts" do
664
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 2)
665
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(2), 3)
666
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 4)
667
- stats = IdentifiedRestrictionJob.stats(true)
668
- stats[:identifiers].should == {"IdentifiedRestrictionJob.1"=>{"running"=>2}, "IdentifiedRestrictionJob.2"=>{"running"=>3}, "ConcurrentRestrictionJob"=>{"running"=>4}}
669
- end
670
-
671
- it "should track lock_count" do
672
- IdentifiedRestrictionJob.acquire_lock(IdentifiedRestrictionJob.lock_key(IdentifiedRestrictionJob.tracking_key(1)), Time.now.to_i)
673
- IdentifiedRestrictionJob.acquire_lock(IdentifiedRestrictionJob.lock_key(IdentifiedRestrictionJob.tracking_key(2)), Time.now.to_i)
674
- ConcurrentRestrictionJob.acquire_lock(ConcurrentRestrictionJob.lock_key(ConcurrentRestrictionJob.tracking_key), Time.now.to_i)
675
- stats = IdentifiedRestrictionJob.stats
676
- stats[:lock_count].should == 3
677
- end
678
-
679
- it "should track runnable_count" do
680
- job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
681
- job2 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => []})
682
- job3 = Resque::Job.new("somequeue", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
683
-
684
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
685
- ConcurrentRestrictionJob.stash_if_restricted(job1)
686
- ConcurrentRestrictionJob.stash_if_restricted(job2)
687
-
688
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 99)
689
- IdentifiedRestrictionJob.stash_if_restricted(job3)
690
-
691
- ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
692
- IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 0)
693
-
694
- stats = IdentifiedRestrictionJob.stats
695
- stats[:runnable_count].should == 2
696
- end
697
-
698
- end
699
-
700
- end
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
+ Resque.redis.get("stat:failed").to_i.should == 0
13
+ end
14
+
15
+ it "should follow the convention" do
16
+ Resque::Plugin.lint(Resque::Plugins::ConcurrentRestrictionJob)
17
+ end
18
+
19
+ context "settings" do
20
+
21
+ it "should allow setting/getting global config for lock_timeout" do
22
+ Resque::Plugins::ConcurrentRestriction.lock_timeout.should == 60
23
+ Resque::Plugins::ConcurrentRestriction.configure do |config|
24
+ config.lock_timeout = 61
25
+ end
26
+ Resque::Plugins::ConcurrentRestriction.lock_timeout.should == 61
27
+ Resque::Plugins::ConcurrentRestriction.lock_timeout = 60
28
+ Resque::Plugins::ConcurrentRestriction.lock_timeout.should == 60
29
+ end
30
+
31
+ it "should allow setting/getting global config for reserve_queued_job_attempts" do
32
+ Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts.should == 1
33
+ Resque::Plugins::ConcurrentRestriction.configure do |config|
34
+ config.reserve_queued_job_attempts = 5
35
+ end
36
+ Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts.should == 5
37
+ Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts = 3
38
+ Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts.should == 3
39
+ end
40
+
41
+ it "should allow setting/getting global config for restricted_before_queued" do
42
+ Resque::Plugins::ConcurrentRestriction.restricted_before_queued.should == false
43
+ Resque::Plugins::ConcurrentRestriction.configure do |config|
44
+ config.restricted_before_queued = true
45
+ end
46
+ Resque::Plugins::ConcurrentRestriction.restricted_before_queued.should == true
47
+ Resque::Plugins::ConcurrentRestriction.restricted_before_queued = false
48
+ Resque::Plugins::ConcurrentRestriction.restricted_before_queued.should == false
49
+ end
50
+
51
+ end
52
+
53
+ context "keys" do
54
+ it "should always contain the classname in tracking_key" do
55
+ ConcurrentRestrictionJob.tracking_key.should == "concurrent.tracking.ConcurrentRestrictionJob"
56
+ IdentifiedRestrictionJob.tracking_key.should == "concurrent.tracking.IdentifiedRestrictionJob"
57
+ IdentifiedRestrictionJob.tracking_key(1).should == "concurrent.tracking.IdentifiedRestrictionJob.1"
58
+ Jobs::NestedRestrictionJob.tracking_key.should == "concurrent.tracking.Jobs::NestedRestrictionJob"
59
+ end
60
+
61
+ it "should be able to get the class from tracking_key" do
62
+ ConcurrentRestrictionJob.tracking_class(ConcurrentRestrictionJob.tracking_key).should == ConcurrentRestrictionJob
63
+ IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key).should == IdentifiedRestrictionJob
64
+ IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key(1)).should == IdentifiedRestrictionJob
65
+ Jobs::NestedRestrictionJob.tracking_class(Jobs::NestedRestrictionJob.tracking_key).should == Jobs::NestedRestrictionJob
66
+ end
67
+
68
+ end
69
+
70
+ context "encode/decode" do
71
+
72
+ it "should encode jobs correctly" do
73
+ job = Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
74
+ JSON.parse(ConcurrentRestrictionJob.encode(job)).should == JSON.parse('{"queue":"somequeue","payload":{"class":"RestrictionJob","args":[1,2,3]}}')
75
+ end
76
+
77
+ it "should decode jobs correctly" do
78
+ ConcurrentRestrictionJob.decode(nil).should == nil
79
+
80
+ job = ConcurrentRestrictionJob.decode('{"queue":"somequeue","payload":{"class":"RestrictionJob","args":[1,2,3]}}')
81
+ job.should == Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
82
+ end
83
+
84
+ it "should rountrip encode/decode jobs correctly" do
85
+ job = Resque::Job.new("somequeue", {"class" => "RestrictionJob", "args" => [1, 2, 3]})
86
+ ConcurrentRestrictionJob.decode(ConcurrentRestrictionJob.encode(job)).should == job
87
+ end
88
+
89
+ end
90
+
91
+ context "locking" do
92
+
93
+ it "should only acquire one lock at a time" do
94
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
95
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
96
+ end
97
+
98
+ it "should expire locks after timeout" do
99
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == true
100
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == false
101
+ sleep 3
102
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == true
103
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 1).should == false
104
+ end
105
+
106
+ it "should release locks" do
107
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
108
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
109
+
110
+ ConcurrentRestrictionJob.release_lock("some_lock_key", Time.now.to_i + 5)
111
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == true
112
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i + 5).should == false
113
+ end
114
+
115
+ it "should not release lock if expired" do
116
+ ConcurrentRestrictionJob.acquire_lock("some_lock_key", Time.now.to_i - 5).should == true
117
+ ConcurrentRestrictionJob.release_lock("some_lock_key", Time.now.to_i - 5)
118
+ Resque.redis.get("some_lock_key").should_not be_nil
119
+ end
120
+
121
+ it "should do a blocking lock while running atomically" do
122
+ counter = nil
123
+
124
+ t1 = Thread.new do
125
+ ConcurrentRestrictionJob.run_atomically("some_lock_key") do
126
+ sleep 0.2
127
+ counter = "first"
128
+ end
129
+ end
130
+
131
+ sleep 0.1
132
+ t1.alive?.should == true
133
+
134
+ t2 = Thread.new do
135
+ ConcurrentRestrictionJob.run_atomically("some_lock_key") do
136
+ t1.alive?.should == false
137
+ counter = "second"
138
+ end
139
+ end
140
+ t2.join
141
+
142
+ t1.join
143
+ counter.should == "second"
144
+ end
145
+
146
+ it "should fail if can't acquire lock within tries" do
147
+ did_run1 = did_run2 = false
148
+
149
+ t1 = Thread.new do
150
+ ConcurrentRestrictionJob.run_atomically("some_lock_key") do
151
+ sleep 0.2
152
+ end
153
+ end
154
+
155
+ sleep 0.1
156
+ t1.alive?.should == true
157
+
158
+ t2 = Thread.new do
159
+ ConcurrentRestrictionJob.run_atomically("some_lock_key", 2) do
160
+ did_run1 = true
161
+ end
162
+ end
163
+ t2.join
164
+
165
+ t3 = Thread.new do
166
+ ConcurrentRestrictionJob.run_atomically("some_lock_key", 100) do
167
+ did_run2 = true
168
+ end
169
+ end
170
+ t3.join
171
+
172
+ t1.join
173
+ did_run1.should be_falsey
174
+ did_run2.should be_truthy
175
+ end
176
+
177
+ end
178
+
179
+ context "#helpers" do
180
+
181
+ it "should mark a job as runnable" do
182
+ IdentifiedRestrictionJob.runnables.should == []
183
+ IdentifiedRestrictionJob.runnables(:somequeue).should == []
184
+ IdentifiedRestrictionJob.runnables(:somequeue2).should == []
185
+
186
+ IdentifiedRestrictionJob.update_queues_available(IdentifiedRestrictionJob.tracking_key(1), :somequeue, :add)
187
+ IdentifiedRestrictionJob.update_queues_available(IdentifiedRestrictionJob.tracking_key(2), :somequeue2, :add)
188
+
189
+ IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(1), true)
190
+ IdentifiedRestrictionJob.runnables(:somequeue).should == [IdentifiedRestrictionJob.tracking_key(1)]
191
+ IdentifiedRestrictionJob.runnables(:somequeue2).should == []
192
+ IdentifiedRestrictionJob.runnables.should == [IdentifiedRestrictionJob.tracking_key(1)]
193
+
194
+ IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(2), true)
195
+ IdentifiedRestrictionJob.runnables(:somequeue).should == [IdentifiedRestrictionJob.tracking_key(1)]
196
+ IdentifiedRestrictionJob.runnables(:somequeue2).should == [IdentifiedRestrictionJob.tracking_key(2)]
197
+ IdentifiedRestrictionJob.runnables.sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)].sort
198
+
199
+ IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(1), false)
200
+ IdentifiedRestrictionJob.runnables(:somequeue).should == []
201
+ IdentifiedRestrictionJob.runnables(:somequeue2).should == [IdentifiedRestrictionJob.tracking_key(2)]
202
+ IdentifiedRestrictionJob.runnables.should == [IdentifiedRestrictionJob.tracking_key(2)]
203
+
204
+ IdentifiedRestrictionJob.mark_runnable(IdentifiedRestrictionJob.tracking_key(2), false)
205
+ IdentifiedRestrictionJob.runnables(:somequeue).should == []
206
+ IdentifiedRestrictionJob.runnables(:somequeue2).should == []
207
+ IdentifiedRestrictionJob.runnables.should == []
208
+ end
209
+
210
+ end
211
+
212
+ context "running count" do
213
+
214
+ it "should set running count" do
215
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
216
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 5)
217
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 5
218
+ end
219
+
220
+ it "should increment running count" do
221
+ ConcurrentRestrictionJob.stub(:concurrent_limit).and_return(2)
222
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
223
+ ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == false
224
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
225
+ ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == true
226
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 2
227
+ ConcurrentRestrictionJob.increment_running_count(ConcurrentRestrictionJob.tracking_key).should == true
228
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 3
229
+ end
230
+
231
+ it "should decrement running count" do
232
+ ConcurrentRestrictionJob.stub(:concurrent_limit).and_return(2)
233
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 3)
234
+ ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == true
235
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 2
236
+ ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
237
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
238
+ ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
239
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
240
+ end
241
+
242
+ it "should not decrement running count below 0" do
243
+ ConcurrentRestrictionJob.stub(:concurrent_limit).and_return(1)
244
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
245
+ ConcurrentRestrictionJob.decrement_running_count(ConcurrentRestrictionJob.tracking_key).should == false
246
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
247
+ end
248
+
249
+ it "should be able to tell when restricted" do
250
+ ConcurrentRestrictionJob.stub(:concurrent_limit).and_return(1)
251
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
252
+ ConcurrentRestrictionJob.restricted?(ConcurrentRestrictionJob.tracking_key).should == false
253
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
254
+ ConcurrentRestrictionJob.restricted?(ConcurrentRestrictionJob.tracking_key).should == true
255
+ end
256
+
257
+ end
258
+
259
+ context "restriction queue" do
260
+
261
+ it "should push jobs to the restriction queue" do
262
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
263
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
264
+ job3 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
265
+ job4 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [4]})
266
+ ConcurrentRestrictionJob.push_to_restriction_queue(job1)
267
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
268
+ ConcurrentRestrictionJob.push_to_restriction_queue(job2)
269
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1, job2]
270
+ ConcurrentRestrictionJob.push_to_restriction_queue(job3, :back)
271
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1, job2, job3]
272
+ ConcurrentRestrictionJob.push_to_restriction_queue(job4, :front)
273
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job4, job1, job2, job3]
274
+ lambda { ConcurrentRestrictionJob.push_to_restriction_queue(job1, :bad) }.should raise_exception
275
+ end
276
+
277
+ it "should pop jobs from restriction queue" do
278
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
279
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
280
+
281
+ ConcurrentRestrictionJob.push_to_restriction_queue(job1)
282
+ ConcurrentRestrictionJob.push_to_restriction_queue(job2)
283
+ popped = ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
284
+ popped.should == job1
285
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job2]
286
+ end
287
+
288
+ it "should add to queue availabilty on push" do
289
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
290
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
291
+ job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
292
+
293
+ ConcurrentRestrictionJob.push_to_restriction_queue(job1)
294
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
295
+
296
+ ConcurrentRestrictionJob.push_to_restriction_queue(job2)
297
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
298
+
299
+ ConcurrentRestrictionJob.push_to_restriction_queue(job3)
300
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
301
+ end
302
+
303
+ it "should clean up queue availabilty on pop" do
304
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
305
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
306
+ job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
307
+ ConcurrentRestrictionJob.push_to_restriction_queue(job1)
308
+ ConcurrentRestrictionJob.push_to_restriction_queue(job2)
309
+ ConcurrentRestrictionJob.push_to_restriction_queue(job3)
310
+
311
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
312
+
313
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
314
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
315
+
316
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
317
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
318
+
319
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
320
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == []
321
+ end
322
+
323
+ it "should ensure runnables on queue when pushed" do
324
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
325
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
326
+ job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
327
+
328
+
329
+ ConcurrentRestrictionJob.push_to_restriction_queue(job1)
330
+ ConcurrentRestrictionJob.push_to_restriction_queue(job2)
331
+ ConcurrentRestrictionJob.push_to_restriction_queue(job3)
332
+
333
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 0)
334
+
335
+ ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
336
+ ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
337
+ ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
338
+
339
+ end
340
+
341
+ it "should remove runnables for queues on pop" do
342
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
343
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
344
+ job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
345
+
346
+ ConcurrentRestrictionJob.push_to_restriction_queue(job1)
347
+ ConcurrentRestrictionJob.push_to_restriction_queue(job2)
348
+ ConcurrentRestrictionJob.push_to_restriction_queue(job3)
349
+ ConcurrentRestrictionJob.stub(:concurrent_limit).and_return(5)
350
+
351
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
352
+ ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
353
+ ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
354
+ ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
355
+
356
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
357
+ ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
358
+ ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
359
+ ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
360
+
361
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
362
+ ConcurrentRestrictionJob.runnables.sort.should == []
363
+ ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
364
+ ConcurrentRestrictionJob.runnables("somequeue2").sort.should == []
365
+ end
366
+
367
+ it "should track queue_counts" do
368
+ ConcurrentRestrictionJob.queue_counts.should == {}
369
+
370
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
371
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
372
+ job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
373
+
374
+ ConcurrentRestrictionJob.push_to_restriction_queue(job1)
375
+ ConcurrentRestrictionJob.push_to_restriction_queue(job2)
376
+ ConcurrentRestrictionJob.push_to_restriction_queue(job3)
377
+ ConcurrentRestrictionJob.queue_counts.should == {"somequeue"=>2, "somequeue2"=>1}
378
+
379
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
380
+ ConcurrentRestrictionJob.queue_counts.should == {"somequeue"=>1, "somequeue2"=>1}
381
+
382
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue")
383
+ ConcurrentRestrictionJob.pop_from_restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2")
384
+ ConcurrentRestrictionJob.queue_counts.should == {"somequeue"=>0, "somequeue2"=>0}
385
+ end
386
+ end
387
+
388
+ context "#stash_if_restricted" do
389
+
390
+ it "should return false and mark running for job that is not restricted" do
391
+ job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
392
+ ConcurrentRestrictionJob.stash_if_restricted(job).should == false
393
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
394
+ end
395
+
396
+ it "should return true and not mark running for job that is restricted" do
397
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 99)
398
+
399
+ job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
400
+ ConcurrentRestrictionJob.stash_if_restricted(job).should == true
401
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 99
402
+ ConcurrentRestrictionJob.runnables.should == []
403
+ end
404
+
405
+ it "should add to queue availabilty on stash when restricted" do
406
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key(1), 99)
407
+
408
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
409
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
410
+ job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
411
+
412
+ ConcurrentRestrictionJob.stash_if_restricted(job1)
413
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
414
+
415
+ ConcurrentRestrictionJob.stash_if_restricted(job2)
416
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue"]
417
+
418
+ ConcurrentRestrictionJob.stash_if_restricted(job3)
419
+ ConcurrentRestrictionJob.queues_available(ConcurrentRestrictionJob.tracking_key).sort.should == ["somequeue", "somequeue2"]
420
+ end
421
+
422
+ it "should map available queues to tracking key on push" do
423
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
424
+
425
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [1]})
426
+ job2 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => [2]})
427
+ job3 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => [3]})
428
+
429
+ ConcurrentRestrictionJob.stash_if_restricted(job1)
430
+ ConcurrentRestrictionJob.stash_if_restricted(job3)
431
+ ConcurrentRestrictionJob.stash_if_restricted(job2)
432
+
433
+ ConcurrentRestrictionJob.runnables.sort.should == []
434
+ ConcurrentRestrictionJob.runnables("somequeue").sort.should == []
435
+ ConcurrentRestrictionJob.runnables("somequeue2").sort.should == []
436
+
437
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
438
+
439
+ ConcurrentRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key]
440
+ ConcurrentRestrictionJob.runnables("somequeue").sort.should == [ConcurrentRestrictionJob.tracking_key]
441
+ ConcurrentRestrictionJob.runnables("somequeue2").sort.should == [ConcurrentRestrictionJob.tracking_key]
442
+
443
+ end
444
+
445
+ it "should repush job and return true if it can't acquire a lock" do
446
+ old = Resque::Plugins::ConcurrentRestriction.lock_tries
447
+ begin
448
+ Resque::Plugins::ConcurrentRestriction.lock_tries = 0
449
+
450
+ job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
451
+ ConcurrentRestrictionJob.stash_if_restricted(job).should == true
452
+ Resque.peek("somequeue").should == {"class" => "ConcurrentRestrictionJob", "args" => []}
453
+ ensure
454
+ Resque::Plugins::ConcurrentRestriction.lock_tries = old
455
+ end
456
+
457
+ end
458
+
459
+ end
460
+
461
+ context "#next_runnable_job" do
462
+
463
+ it "should do nothing when nothing runnable" do
464
+ ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
465
+ end
466
+
467
+ it "should return nil and not pop from queue if cannot acquire lock" do
468
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
469
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
470
+ ConcurrentRestrictionJob.stash_if_restricted(job1)
471
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
472
+
473
+ old = Resque::Plugins::ConcurrentRestriction.lock_tries
474
+ begin
475
+ Resque::Plugins::ConcurrentRestriction.lock_tries = 0
476
+
477
+ ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
478
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
479
+ ensure
480
+ Resque::Plugins::ConcurrentRestriction.lock_tries = old
481
+ end
482
+ end
483
+
484
+ it "should not get a job if nothing runnable" do
485
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
486
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
487
+ ConcurrentRestrictionJob.stash_if_restricted(job1)
488
+ ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
489
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == [job1]
490
+ end
491
+
492
+ it "should get a job if something runnable" do
493
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
494
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
495
+ ConcurrentRestrictionJob.stash_if_restricted(job1)
496
+
497
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
498
+ ConcurrentRestrictionJob.next_runnable_job('somequeue').should == job1
499
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue").should == []
500
+ end
501
+
502
+ it "should not get a job if something runnable on other queue" do
503
+ job1 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => []})
504
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
505
+ ConcurrentRestrictionJob.stash_if_restricted(job1)
506
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
507
+
508
+ ConcurrentRestrictionJob.next_runnable_job('somequeue').should be_nil
509
+ ConcurrentRestrictionJob.restriction_queue(ConcurrentRestrictionJob.tracking_key, "somequeue2").should == [job1]
510
+ end
511
+
512
+ it "should get a job for right class when called through ConcurrentRestrictionJob" do
513
+ job1 = Resque::Job.new("somequeue", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
514
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 99)
515
+ IdentifiedRestrictionJob.stash_if_restricted(job1)
516
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 0)
517
+
518
+ # Use ConcurrentRestrictionJob as thats what Resque::Worker::reserve extension has to use
519
+ ConcurrentRestrictionJob.next_runnable_job('somequeue').should == job1
520
+ end
521
+
522
+ end
523
+
524
+ context "#release_restriction" do
525
+
526
+ it "should decrement running count on release restriction" do
527
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
528
+ job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
529
+ ConcurrentRestrictionJob.release_restriction(job)
530
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
531
+ end
532
+
533
+ it "should keep restriction above 0" do
534
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
535
+ job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
536
+ ConcurrentRestrictionJob.release_restriction(job)
537
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
538
+ end
539
+
540
+ it "should do nothing if cannot acquire lock" do
541
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 1)
542
+ job = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
543
+
544
+ old = Resque::Plugins::ConcurrentRestriction.lock_tries
545
+ begin
546
+ Resque::Plugins::ConcurrentRestriction.lock_tries = 0
547
+
548
+ ConcurrentRestrictionJob.release_restriction(job)
549
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 1
550
+ ensure
551
+ Resque::Plugins::ConcurrentRestriction.lock_tries = old
552
+ end
553
+ end
554
+
555
+ end
556
+
557
+ context "#reset_restrictions" do
558
+ it "should do nothing when nothing is in redis" do
559
+ ConcurrentRestrictionJob.reset_restrictions.should == [0, 0]
560
+ end
561
+
562
+ it "should reset counts" do
563
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 5)
564
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 5)
565
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(2), 5)
566
+
567
+ ConcurrentRestrictionJob.reset_restrictions.should == [3, 0]
568
+ ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
569
+ IdentifiedRestrictionJob.running_count(IdentifiedRestrictionJob.tracking_key(1)).should == 0
570
+ IdentifiedRestrictionJob.running_count(IdentifiedRestrictionJob.tracking_key(2)).should == 0
571
+ end
572
+
573
+ it "should reset restriction queue runnable state" do
574
+ job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
575
+ job2 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
576
+ job3 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
577
+ job4 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
578
+ job5 = Resque::Job.new("queue3", {"class" => "ConcurrentRestrictionJob", "args" => []})
579
+
580
+ IdentifiedRestrictionJob.push_to_restriction_queue(job1)
581
+ IdentifiedRestrictionJob.push_to_restriction_queue(job2)
582
+ IdentifiedRestrictionJob.push_to_restriction_queue(job3)
583
+ IdentifiedRestrictionJob.push_to_restriction_queue(job4)
584
+ ConcurrentRestrictionJob.push_to_restriction_queue(job5)
585
+
586
+ ConcurrentRestrictionJob.reset_restrictions.should == [0, 5]
587
+ IdentifiedRestrictionJob.runnables(:queue1).sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
588
+ IdentifiedRestrictionJob.runnables(:queue2).sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
589
+ ConcurrentRestrictionJob.runnables(:queue3).sort.should == [ConcurrentRestrictionJob.tracking_key]
590
+
591
+ IdentifiedRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key, IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
592
+
593
+ ConcurrentRestrictionJob.queue_counts.should == {"queue1"=>2, "queue2"=>2, "queue3"=>1}
594
+
595
+ end
596
+
597
+ it "should handle a large amount of concurrent keys" do
598
+ # the ruby splat operator will choke on an arguments list greater than a couple hundred thousand objects. make sure this case is handled correctly
599
+ # It might be better to actually populate redis with a bunch keys but that makes the test pretty slow
600
+
601
+ # we have to keep this splat limitation in mind when populating test data, too
602
+ concurrent_count_keys = 20001.times.collect{ |i| ["concurrent.count.#{i}", "#{i}"] }.flatten
603
+ concurrent_count_keys.each_slice(10000) do |slice|
604
+ Resque.redis.mset *slice
605
+ end
606
+
607
+ concurrent_runnable_keys = 20001.times.collect{ |i| ["concurrent.runnable.#{i}", "#{i}"] }.flatten
608
+ concurrent_runnable_keys.each_slice(10000) do |slice|
609
+ Resque.redis.mset *slice
610
+ end
611
+
612
+ return_value = nil
613
+
614
+ lambda{ return_value = ConcurrentRestrictionJob.reset_restrictions }.should_not raise_exception
615
+
616
+ return_value.should == [20001, 0]
617
+ end
618
+
619
+ end
620
+
621
+ context "#stats" do
622
+
623
+ it "should have blank info when nothing going on" do
624
+ stats = ConcurrentRestrictionJob.stats
625
+ stats[:queues].should == {}
626
+ stats[:identifiers].should == {}
627
+ stats[:lock_count].should == 0
628
+ stats[:runnable_count].should == 0
629
+ estats = ConcurrentRestrictionJob.stats(true)
630
+ estats.should == stats
631
+ end
632
+
633
+ it "should not track identifiers when not extended" do
634
+ job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
635
+ IdentifiedRestrictionJob.push_to_restriction_queue(job1)
636
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 2)
637
+
638
+ stats = IdentifiedRestrictionJob.stats
639
+ stats[:identifiers].should == {}
640
+ end
641
+
642
+ it "should track queue_totals" do
643
+ job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
644
+ job2 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
645
+ job3 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
646
+ job4 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
647
+ job5 = Resque::Job.new("queue3", {"class" => "ConcurrentRestrictionJob", "args" => []})
648
+
649
+ IdentifiedRestrictionJob.push_to_restriction_queue(job1)
650
+ IdentifiedRestrictionJob.push_to_restriction_queue(job2)
651
+ IdentifiedRestrictionJob.push_to_restriction_queue(job3)
652
+ IdentifiedRestrictionJob.push_to_restriction_queue(job4)
653
+ ConcurrentRestrictionJob.push_to_restriction_queue(job5)
654
+
655
+ stats = IdentifiedRestrictionJob.stats(true)
656
+ stats[:queues].should == {"queue1" => 2, "queue2" => 2, "queue3" => 1}
657
+
658
+ stats[:identifiers].should == {"IdentifiedRestrictionJob.1"=>{"queue1"=>1, "queue2"=>1}, "IdentifiedRestrictionJob.2"=>{"queue1"=>1, "queue2"=>1}, "ConcurrentRestrictionJob"=>{"queue3"=>1}}
659
+ end
660
+
661
+ it "should track running_counts" do
662
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 2)
663
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(2), 3)
664
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 4)
665
+ stats = IdentifiedRestrictionJob.stats(true)
666
+ stats[:identifiers].should == {"IdentifiedRestrictionJob.1"=>{"running"=>2}, "IdentifiedRestrictionJob.2"=>{"running"=>3}, "ConcurrentRestrictionJob"=>{"running"=>4}}
667
+ end
668
+
669
+ it "should track lock_count" do
670
+ IdentifiedRestrictionJob.acquire_lock(IdentifiedRestrictionJob.lock_key(IdentifiedRestrictionJob.tracking_key(1)), Time.now.to_i)
671
+ IdentifiedRestrictionJob.acquire_lock(IdentifiedRestrictionJob.lock_key(IdentifiedRestrictionJob.tracking_key(2)), Time.now.to_i)
672
+ ConcurrentRestrictionJob.acquire_lock(ConcurrentRestrictionJob.lock_key(ConcurrentRestrictionJob.tracking_key), Time.now.to_i)
673
+ stats = IdentifiedRestrictionJob.stats
674
+ stats[:lock_count].should == 3
675
+ end
676
+
677
+ it "should track runnable_count" do
678
+ job1 = Resque::Job.new("somequeue", {"class" => "ConcurrentRestrictionJob", "args" => []})
679
+ job2 = Resque::Job.new("somequeue2", {"class" => "ConcurrentRestrictionJob", "args" => []})
680
+ job3 = Resque::Job.new("somequeue", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
681
+
682
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 99)
683
+ ConcurrentRestrictionJob.stash_if_restricted(job1)
684
+ ConcurrentRestrictionJob.stash_if_restricted(job2)
685
+
686
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 99)
687
+ IdentifiedRestrictionJob.stash_if_restricted(job3)
688
+
689
+ ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 0)
690
+ IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 0)
691
+
692
+ stats = IdentifiedRestrictionJob.stats
693
+ stats[:runnable_count].should == 2
694
+ end
695
+
696
+ end
697
+
698
+ end