resque-concurrent-restriction 0.5.9 → 0.6.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.
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