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:lock:#{parts[2..-1].join(':')}"
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:count:#{parts[2..-1].join(':')}"
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:queue:#{queue}:#{parts[2..-1].join(':')}"
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:queue_availability:#{parts[2..-1].join(':')}"
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 = ":#{id}" if id && id.strip.size > 0
52
- "concurrent:tracking:#{self.to_s}#{id}"
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(":")[2])
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 = ":#{queue}" if queue
62
- "concurrent:runnable#{key}"
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:queue:*")
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:count:*")
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:lock:*")
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)
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module ConcurrentRestriction
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.1"
5
5
  end
6
6
  end
7
7
  end
@@ -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:tracking:ConcurrentRestrictionJob"
22
- IdentifiedRestrictionJob.tracking_key.should == "concurrent:tracking:IdentifiedRestrictionJob"
23
- IdentifiedRestrictionJob.tracking_key(1).should == "concurrent:tracking:IdentifiedRestrictionJob:1"
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:1" => 2, "IdentifiedRestrictionJob:2" => 2, "ConcurrentRestrictionJob" => 1}
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:1" => 2, "IdentifiedRestrictionJob:2" => 3, "ConcurrentRestrictionJob" => 4}
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
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: resque-concurrent-restriction
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.5.0
5
+ version: 0.5.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Matt Conway