resque-concurrent-restriction 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,43 @@ module Resque
|
|
2
2
|
module Plugins
|
3
3
|
module ConcurrentRestriction
|
4
4
|
|
5
|
+
# Redis Data Structures
|
6
|
+
#
|
7
|
+
# concurrent.lock.tracking_id => timestamp
|
8
|
+
# Maintains the distributed lock for the tracking_key to ensure
|
9
|
+
# atomic modification of other data structures
|
10
|
+
#
|
11
|
+
# concurrent.count.tracking_id => count
|
12
|
+
# The count of currently running jobs for the tracking_id
|
13
|
+
#
|
14
|
+
# concurrent.queue.queue_name.tracking_id => List[job1, job2, ...]
|
15
|
+
# The queue of items that is currently unable to run due to count being exceeded
|
16
|
+
#
|
17
|
+
# concurrent.queue_availability.tracking_key => Set[queue_name1, queue_name2, ...]
|
18
|
+
# Maintains the set of queues that currently have something
|
19
|
+
# runnable for each tracking_id
|
20
|
+
#
|
21
|
+
# concurrent.runnable[.queue_name] => Set[tracking_id1, tracking_id2, ...]
|
22
|
+
# Maintains the set of tracking_ids that have something
|
23
|
+
# runnable for each queue (globally without .queue_name postfix in key)
|
24
|
+
#
|
25
|
+
# The behavior has two points of entry:
|
26
|
+
#
|
27
|
+
# When the Resque::Worker is looking for a job to run from a restriction
|
28
|
+
# queue, we use the queue_name to look up the set of tracking IDs that
|
29
|
+
# are currently runnable for that queue. If we get a tracking id, we
|
30
|
+
# know that there is a restriction queue with something runnable in it,
|
31
|
+
# and we then use that tracking_id and queue to look up and pop something
|
32
|
+
# off of the restriction queue.
|
33
|
+
#
|
34
|
+
# When the Resque::Worker gets a job off of a normal resque queue, it uses
|
35
|
+
# the count to see if that job is currently restricted. If not, it runs it
|
36
|
+
# as normal, but if it is restricted, then it sticks it on a restriction queue.
|
37
|
+
#
|
38
|
+
# In both cases, before a job is handed off to resque to be run, we increment
|
39
|
+
# the count so we can keep tracking of how many are currently running. When
|
40
|
+
# the job finishes, we then decrement the count.
|
41
|
+
|
5
42
|
# Used by the user in their job class to set the concurrency limit
|
6
43
|
def concurrent(limit)
|
7
44
|
@concurrent = limit
|
@@ -20,26 +57,26 @@ module Resque
|
|
20
57
|
# The key used to acquire a lock so we can operate on multiple
|
21
58
|
# redis structures (runnables set, running_count) atomically
|
22
59
|
def lock_key(tracking_key)
|
23
|
-
parts = tracking_key.split("
|
24
|
-
"concurrent
|
60
|
+
parts = tracking_key.split(".")
|
61
|
+
"concurrent.lock.#{parts[2..-1].join('.')}"
|
25
62
|
end
|
26
63
|
|
27
64
|
# The redis key used to store the number of currently running
|
28
65
|
# jobs for the restriction_identifier
|
29
66
|
def running_count_key(tracking_key)
|
30
|
-
parts = tracking_key.split("
|
31
|
-
"concurrent
|
67
|
+
parts = tracking_key.split(".")
|
68
|
+
"concurrent.count.#{parts[2..-1].join('.')}"
|
32
69
|
end
|
33
70
|
|
34
71
|
# The key for the redis list where restricted jobs for the given resque queue are stored
|
35
72
|
def restriction_queue_key(tracking_key, queue)
|
36
|
-
parts = tracking_key.split("
|
37
|
-
"concurrent
|
73
|
+
parts = tracking_key.split(".")
|
74
|
+
"concurrent.queue.#{queue}.#{parts[2..-1].join('.')}"
|
38
75
|
end
|
39
76
|
|
40
77
|
def restriction_queue_availability_key(tracking_key)
|
41
|
-
parts = tracking_key.split("
|
42
|
-
"concurrent
|
78
|
+
parts = tracking_key.split(".")
|
79
|
+
"concurrent.queue_availability.#{parts[2..-1].join('.')}"
|
43
80
|
end
|
44
81
|
|
45
82
|
# The key that groups all jobs of the same restriction_identifier together
|
@@ -48,18 +85,18 @@ module Resque
|
|
48
85
|
# for those queues are stored
|
49
86
|
def tracking_key(*args)
|
50
87
|
id = concurrent_identifier(*args)
|
51
|
-
id = "
|
52
|
-
"concurrent
|
88
|
+
id = ".#{id}" if id && id.strip.size > 0
|
89
|
+
"concurrent.tracking.#{self.to_s}#{id}"
|
53
90
|
end
|
54
91
|
|
55
92
|
def tracking_class(tracking_key)
|
56
|
-
Resque.constantize(tracking_key.split("
|
93
|
+
Resque.constantize(tracking_key.split(".")[2])
|
57
94
|
end
|
58
95
|
|
59
96
|
# The key to the redis set where we keep a list of runnable tracking_keys
|
60
97
|
def runnables_key(queue=nil)
|
61
|
-
key = "
|
62
|
-
"concurrent
|
98
|
+
key = ".#{queue}" if queue
|
99
|
+
"concurrent.runnable#{key}"
|
63
100
|
end
|
64
101
|
|
65
102
|
# Encodes the job intot he restriction queue
|
@@ -328,16 +365,44 @@ module Resque
|
|
328
365
|
end
|
329
366
|
end
|
330
367
|
|
368
|
+
# Resets everything to be runnable
|
369
|
+
def reset_restrictions
|
370
|
+
|
371
|
+
counts_reset = 0
|
372
|
+
count_keys = Resque.redis.keys("concurrent.count.*")
|
373
|
+
counts_reset = Resque.redis.del(*count_keys) if count_keys.size > 0
|
374
|
+
|
375
|
+
runnable_keys = Resque.redis.keys("concurrent.runnable*")
|
376
|
+
Resque.redis.del(*runnable_keys) if runnable_keys.size > 0
|
377
|
+
|
378
|
+
queues_enabled = 0
|
379
|
+
queue_keys = Resque.redis.keys("concurrent.queue.*")
|
380
|
+
queue_keys.each do |k|
|
381
|
+
if Resque.redis.llen(k) > 0
|
382
|
+
parts = k.split(".")
|
383
|
+
queue = parts[2]
|
384
|
+
ident = parts[3..-1].join('.')
|
385
|
+
tracking_key = "concurrent.tracking.#{ident}"
|
386
|
+
update_queues_available(tracking_key, queue, :add)
|
387
|
+
mark_runnable(tracking_key, true)
|
388
|
+
queues_enabled += 1
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
return counts_reset, queues_enabled
|
393
|
+
|
394
|
+
end
|
395
|
+
|
331
396
|
def stats
|
332
397
|
results = {}
|
333
398
|
|
334
|
-
queue_keys = Resque.redis.keys("concurrent
|
399
|
+
queue_keys = Resque.redis.keys("concurrent.queue.*")
|
335
400
|
|
336
401
|
queue_sizes = {}
|
337
402
|
ident_sizes = {}
|
338
403
|
queue_keys.each do |k|
|
339
|
-
parts = k.split("
|
340
|
-
ident = parts[3..-1].join("
|
404
|
+
parts = k.split(".")
|
405
|
+
ident = parts[3..-1].join(".")
|
341
406
|
queue_name = parts[2]
|
342
407
|
size = Resque.redis.llen(k)
|
343
408
|
queue_sizes[queue_name] ||= 0
|
@@ -346,15 +411,15 @@ module Resque
|
|
346
411
|
ident_sizes[ident] += size
|
347
412
|
end
|
348
413
|
|
349
|
-
count_keys = Resque.redis.keys("concurrent
|
414
|
+
count_keys = Resque.redis.keys("concurrent.count.*")
|
350
415
|
running_counts = {}
|
351
416
|
count_keys.each do |k|
|
352
|
-
parts = k.split("
|
353
|
-
ident = parts[2..-1].join("
|
417
|
+
parts = k.split(".")
|
418
|
+
ident = parts[2..-1].join(".")
|
354
419
|
running_counts[ident] = Resque.redis.get(k).to_i
|
355
420
|
end
|
356
421
|
|
357
|
-
lock_keys = Resque.redis.keys("concurrent
|
422
|
+
lock_keys = Resque.redis.keys("concurrent.lock.*")
|
358
423
|
lock_count = lock_keys.size
|
359
424
|
|
360
425
|
runnable_count = Resque.redis.scard(runnables_key)
|
@@ -18,15 +18,17 @@ describe Resque::Plugins::ConcurrentRestriction do
|
|
18
18
|
|
19
19
|
context "keys" do
|
20
20
|
it "should always contain the classname in tracking_key" do
|
21
|
-
ConcurrentRestrictionJob.tracking_key.should == "concurrent
|
22
|
-
IdentifiedRestrictionJob.tracking_key.should == "concurrent
|
23
|
-
IdentifiedRestrictionJob.tracking_key(1).should == "concurrent
|
21
|
+
ConcurrentRestrictionJob.tracking_key.should == "concurrent.tracking.ConcurrentRestrictionJob"
|
22
|
+
IdentifiedRestrictionJob.tracking_key.should == "concurrent.tracking.IdentifiedRestrictionJob"
|
23
|
+
IdentifiedRestrictionJob.tracking_key(1).should == "concurrent.tracking.IdentifiedRestrictionJob.1"
|
24
|
+
Jobs::NestedRestrictionJob.tracking_key.should == "concurrent.tracking.Jobs::NestedRestrictionJob"
|
24
25
|
end
|
25
26
|
|
26
27
|
it "should be able to get the class from tracking_key" do
|
27
28
|
ConcurrentRestrictionJob.tracking_class(ConcurrentRestrictionJob.tracking_key).should == ConcurrentRestrictionJob
|
28
29
|
IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key).should == IdentifiedRestrictionJob
|
29
30
|
IdentifiedRestrictionJob.tracking_class(IdentifiedRestrictionJob.tracking_key(1)).should == IdentifiedRestrictionJob
|
31
|
+
Jobs::NestedRestrictionJob.tracking_class(Jobs::NestedRestrictionJob.tracking_key).should == Jobs::NestedRestrictionJob
|
30
32
|
end
|
31
33
|
|
32
34
|
end
|
@@ -418,6 +420,46 @@ describe Resque::Plugins::ConcurrentRestriction do
|
|
418
420
|
|
419
421
|
end
|
420
422
|
|
423
|
+
context "#reset_restrictions" do
|
424
|
+
it "should do nothing when nothing is in redis" do
|
425
|
+
ConcurrentRestrictionJob.reset_restrictions.should == [0, 0]
|
426
|
+
end
|
427
|
+
|
428
|
+
it "should reset counts" do
|
429
|
+
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 5)
|
430
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(1), 5)
|
431
|
+
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(2), 5)
|
432
|
+
|
433
|
+
ConcurrentRestrictionJob.reset_restrictions.should == [3, 0]
|
434
|
+
ConcurrentRestrictionJob.running_count(ConcurrentRestrictionJob.tracking_key).should == 0
|
435
|
+
IdentifiedRestrictionJob.running_count(IdentifiedRestrictionJob.tracking_key(1)).should == 0
|
436
|
+
IdentifiedRestrictionJob.running_count(IdentifiedRestrictionJob.tracking_key(2)).should == 0
|
437
|
+
end
|
438
|
+
|
439
|
+
it "should reset restriction queue runnable state" do
|
440
|
+
job1 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
|
441
|
+
job2 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [1]})
|
442
|
+
job3 = Resque::Job.new("queue1", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
|
443
|
+
job4 = Resque::Job.new("queue2", {"class" => "IdentifiedRestrictionJob", "args" => [2]})
|
444
|
+
job5 = Resque::Job.new("queue3", {"class" => "ConcurrentRestrictionJob", "args" => []})
|
445
|
+
|
446
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job1)
|
447
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job2)
|
448
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job3)
|
449
|
+
IdentifiedRestrictionJob.push_to_restriction_queue(job4)
|
450
|
+
ConcurrentRestrictionJob.push_to_restriction_queue(job5)
|
451
|
+
|
452
|
+
ConcurrentRestrictionJob.reset_restrictions.should == [0, 5]
|
453
|
+
IdentifiedRestrictionJob.runnables(:queue1).sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
|
454
|
+
IdentifiedRestrictionJob.runnables(:queue2).sort.should == [IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
|
455
|
+
ConcurrentRestrictionJob.runnables(:queue3).sort.should == [ConcurrentRestrictionJob.tracking_key]
|
456
|
+
|
457
|
+
IdentifiedRestrictionJob.runnables.sort.should == [ConcurrentRestrictionJob.tracking_key, IdentifiedRestrictionJob.tracking_key(1), IdentifiedRestrictionJob.tracking_key(2)]
|
458
|
+
|
459
|
+
end
|
460
|
+
|
461
|
+
end
|
462
|
+
|
421
463
|
context "#stats" do
|
422
464
|
|
423
465
|
it "should have blank info when nothing going on" do
|
@@ -444,7 +486,7 @@ describe Resque::Plugins::ConcurrentRestriction do
|
|
444
486
|
|
445
487
|
stats = IdentifiedRestrictionJob.stats
|
446
488
|
stats[:queue_totals][:by_queue_name].should == {"queue1" => 2, "queue2" => 2, "queue3" => 1}
|
447
|
-
stats[:queue_totals][:by_identifier].should == {"IdentifiedRestrictionJob
|
489
|
+
stats[:queue_totals][:by_identifier].should == {"IdentifiedRestrictionJob.1" => 2, "IdentifiedRestrictionJob.2" => 2, "ConcurrentRestrictionJob" => 1}
|
448
490
|
end
|
449
491
|
|
450
492
|
it "should track running_counts" do
|
@@ -452,7 +494,7 @@ describe Resque::Plugins::ConcurrentRestriction do
|
|
452
494
|
IdentifiedRestrictionJob.set_running_count(IdentifiedRestrictionJob.tracking_key(2), 3)
|
453
495
|
ConcurrentRestrictionJob.set_running_count(ConcurrentRestrictionJob.tracking_key, 4)
|
454
496
|
stats = IdentifiedRestrictionJob.stats
|
455
|
-
stats[:running_counts].should == {"IdentifiedRestrictionJob
|
497
|
+
stats[:running_counts].should == {"IdentifiedRestrictionJob.1" => 2, "IdentifiedRestrictionJob.2" => 3, "ConcurrentRestrictionJob" => 4}
|
456
498
|
end
|
457
499
|
|
458
500
|
it "should track lock_count" do
|
data/spec/spec_helper.rb
CHANGED
@@ -115,6 +115,15 @@ class RestrictionJob
|
|
115
115
|
@queue = 'normal'
|
116
116
|
end
|
117
117
|
|
118
|
+
module Jobs
|
119
|
+
class NestedRestrictionJob
|
120
|
+
extend RunCountHelper
|
121
|
+
extend Resque::Plugins::ConcurrentRestriction
|
122
|
+
concurrent 1
|
123
|
+
@queue = 'normal'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
118
127
|
class IdentifiedRestrictionJob
|
119
128
|
extend RunCountHelper
|
120
129
|
extend Resque::Plugins::ConcurrentRestriction
|