qmore 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 34dc454ee618a38dedc1f352dfaa2165cbd8f399
4
+ data.tar.gz: 81c85cdf9bd56911966eb74b6ee6e7fcc0530241
5
+ SHA512:
6
+ metadata.gz: e96673443f8265ed6efb052532c15675fa3d52ed443623d1ce4b673cee527afc4d60495b6434ddd04fe8e98ac1a0d79915a125b8fc23d40e67aedda33b2621a4
7
+ data.tar.gz: fdb5218c001f5995fa0640e07e7ac89b2bd82dd36ad4f78af29e952d0e848a1300302341cf65bb576cf9f2e913d699c78257ac2e7c3e5d9b1c6a8cccd6b156f3
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ 0.7.0 (06/23/2014)
2
+ ------------------
3
+
4
+ Refactoring of qmore reserver to break it into its component pieces
5
+ making it more flexible.
6
+
7
+ Added strategies for the reservers:
8
+ Sources - where we pull the Qless::Queue from (Enumeration that returns Qless:Queue)
9
+ Filters - filters out queues from Sources based on some criteria (acts as a Source)
10
+ Ordering- Reorders the results of an iteration through an enumeration.
11
+
12
+ Added a Delegating Reserver which delegates down to a collect of reservers to
13
+ find jobs.
14
+
15
+ Added Background Source which updates its available queues asynchronously
16
+
1
17
  0.6.3 (06/13/2014)
2
18
  ------------------
3
19
 
data/README.md CHANGED
@@ -8,11 +8,11 @@ Authored against Qless 0.9.3, so it at least works with that - try running the t
8
8
  Usage
9
9
  -----
10
10
 
11
- To use the rake tasks built into qless, just adding qless to your Gemfile should cause it to get required before the task executes. If you aren't using a gemfile, then you'll need to require qmore directly so that it sets up ENV['JOB_RESERVER'] to use Qmore::JobReserver.
11
+ To use the rake tasks built into qless, just adding qless to your Gemfile should cause it to get required before the task executes. If you aren't using a gemfile, then you'll need to require qmore directly so that it sets up ENV['JOB_RESERVER'] to use Qmore::Reservers::Default.
12
12
 
13
13
  Alternatively, if you have some other way of launching workers (e.g. qless-pool), you can assign the reserver explicitly in the setup rake task or some other initializer:
14
14
 
15
- Qless::Pool.pool_factory.reserver_class = Qmore::JobReserver
15
+ Qless::Pool.pool_factory.reserver_class = Qmore::Reservers::Default
16
16
  Qmore.client == Qless::Pool.pool_factory.client
17
17
 
18
18
  # Enabling Monitoring
@@ -129,3 +129,4 @@ Contributors
129
129
 
130
130
  Matt Conway ( https://github.com/wr0ngway )
131
131
  Bert Goethals ( https://github.com/Bertg )
132
+ James Lawrence ( https://github.com/jambli )
@@ -4,10 +4,9 @@ require 'gem_logger'
4
4
  require 'qmore/configuration'
5
5
  require 'qmore/persistence'
6
6
  require 'qmore/attributes'
7
- require 'qmore/job_reserver'
7
+ require 'qmore/reservers'
8
8
 
9
9
  module Qmore
10
-
11
10
  def self.client=(client)
12
11
  @client = client
13
12
  end
@@ -43,7 +42,7 @@ end
43
42
 
44
43
  module Qless
45
44
  module JobReservers
46
- QmoreReserver = Qmore::JobReserver
45
+ QmoreReserver = Qmore::Reservers::Default
47
46
  end
48
47
  end
49
48
  ENV['JOB_RESERVER'] ||= 'QmoreReserver'
@@ -0,0 +1,7 @@
1
+ module Qmore
2
+ module Reservers
3
+ require 'qmore/reservers/strategies'
4
+ require 'qmore/reservers/default'
5
+ require 'qmore/reservers/delegating'
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ module Qmore::Reservers
2
+ # @param [Enumerable] queues - qless queues to check for work
3
+ class Default < Struct.new(:queues)
4
+ def description
5
+ @description ||= queues.collect(&:name).uniq.join(', ') + " (qmore)"
6
+ end
7
+
8
+ def prep_for_work!
9
+ # nothing here on purpose
10
+ end
11
+
12
+ def reserve
13
+ queues.each do |queue|
14
+ if (job = queue.pop)
15
+ return job
16
+ end
17
+ end
18
+ nil
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ module Qmore::Reservers
2
+ # @param [Enumerable] reservers - a set of reservers
3
+ # to check for work.
4
+ class Delegating < Struct.new(:reservers)
5
+ def description
6
+ "Delegating Reserver"
7
+ end
8
+
9
+ def prep_for_work!
10
+ # nothing here on purpose
11
+ end
12
+
13
+ def queues
14
+ Enumerator.new do |yielder|
15
+ reservers.each do |reserver|
16
+ reserver.queues.each {|queue| yielder << queue}
17
+ end
18
+ end
19
+ end
20
+
21
+ def reserve
22
+ reservers.each do |reserver|
23
+ if (job = reserver.reserve)
24
+ return job
25
+ end
26
+ end
27
+ nil
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,7 @@
1
+ module Qmore::Reservers
2
+ module Strategies
3
+ require 'qmore/reservers/strategies/filtering'
4
+ require 'qmore/reservers/strategies/ordering'
5
+ require 'qmore/reservers/strategies/sources'
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ module Qmore::Reservers::Strategies
2
+ module Filtering
3
+ extend Qmore::Attributes
4
+ # @param [Enumerable] queues - a source of queues
5
+ # @param [Array] regexes - a list of regexes to match against.
6
+ # Return an enumerator of the filtered queues in
7
+ # in prioritized order.
8
+ def self.default(queues, regexes)
9
+ Enumerator.new do |yielder|
10
+ # Map queues to their names
11
+ mapped_queues = queues.reduce({}) do |hash,queue|
12
+ hash[queue.name] = queue
13
+ hash
14
+ end
15
+
16
+ # Filter the queue names against the regexes provided.
17
+ matches = Filtering.expand_queues(regexes, mapped_queues.keys)
18
+
19
+ # Prioritize the queues.
20
+ prioritized_names = Filtering.prioritize_queues(Qmore.configuration.priority_buckets, matches)
21
+
22
+ prioritized_names.each do |name|
23
+ yielder << mapped_queues[name]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module Qmore::Reservers::Strategies::Ordering
2
+ # Shuffles the underlying enumerable
3
+ # for each iteration.
4
+ # @param [Enumerable] enumerable - underlying enumerator to iterate over
5
+ def self.shuffled(enumerable)
6
+ Enumerator.new do |yielder|
7
+ enumerable.to_a.shuffle.each do |e|
8
+ yielder << e
9
+ end
10
+ end
11
+ end
12
+
13
+ # Samples a subset of the underlying enumerable
14
+ # for each iteration.
15
+ # @param [Enumerable] enumerable - underlying enumerator to iterate over
16
+ # @param [Integer] sample_size - number of items to take per iteration
17
+ def self.sampled(enumerable, sample_size = 5)
18
+ Enumerator.new do |yielder|
19
+ enumerable.to_a.sample(sample_size).each do |e|
20
+ yielder << e
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,57 @@
1
+ # This module provides the different kinds of queue sources used by qmore
2
+ module Qmore::Reservers::Strategies::Sources
3
+ # Direct source uses a client to generate the queues we should
4
+ # pull work from. Ignores any queues that do not have tasks available.
5
+ def self.direct(client)
6
+ Enumerator.new do |yielder|
7
+ queues = client.queues.counts.reject do |queue|
8
+ total = %w(waiting recurring depends stalled scheduled).inject(0) { |sum, state| sum += queue[state].to_i }
9
+ total == 0
10
+ end
11
+
12
+ queues.each do |queue|
13
+ yielder << client.queues[queue['name']]
14
+ end
15
+ end
16
+ end
17
+
18
+ # Background Queue source runs in a background thread
19
+ # to periodically update the queues available.
20
+ class Background
21
+ include Enumerable
22
+ attr_reader :delegate, :delay
23
+ # @param [Enumerator] delegate queue source to load the queues from.
24
+ # @param [Integer] delay - how long between updates
25
+ def initialize(delegate, delay)
26
+ @delegate = delegate
27
+ @delay = delay
28
+ end
29
+
30
+ # Spawns a thread to periodically update the
31
+ # queues.
32
+ # @return [Thread] returns the spawned thread.
33
+ def start
34
+ @stop = false
35
+ @queues = delegate.to_a
36
+ Thread.new do
37
+ begin
38
+ loop do
39
+ sleep delay
40
+ break if @stop
41
+ @queues = delegate.to_a
42
+ end
43
+ rescue => e
44
+ retry
45
+ end
46
+ end
47
+ end
48
+
49
+ def stop
50
+ @stop = true
51
+ end
52
+
53
+ def each(&block)
54
+ @queues.each { |q| block.call(q) }
55
+ end
56
+ end
57
+ end
@@ -1,3 +1,3 @@
1
1
  module Qmore
2
- VERSION = "0.6.3"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -164,9 +164,9 @@ describe "Attributes" do
164
164
  priority_buckets = [{'pattern' => 'other*', 'fairly' => true},
165
165
  {'pattern' => 'default', 'fairly' => false}]
166
166
  queues = prioritize_queues(priority_buckets, @real_queues)
167
- queues[0..4].sort.should == others.sort
168
- queues[5..-1].should == ["high_x", "foo", "high_y", "superhigh_z"]
169
- queues.should_not == others.sort + ["high_x", "foo", "high_y", "superhigh_z"]
167
+ (queues[0..4].sort).should eq(others.sort)
168
+ queues[5..-1].should eq(["high_x", "foo", "high_y", "superhigh_z"])
169
+ expect(queues).should_not eq(others.sort + ["high_x", "foo", "high_y", "superhigh_z"])
170
170
  end
171
171
  end
172
172
  end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ describe "Reservers::Default" do
4
+ before(:each) do
5
+ Qmore.client.redis.flushall
6
+ Qmore.configuration = Qmore::Configuration.new
7
+ end
8
+
9
+ it "can reserve from multiple queues" do
10
+ high_queue = Qmore.client.queues['high']
11
+ critical_queue = Qmore.client.queues['critical']
12
+
13
+ high_queue.put(SomeJob, {})
14
+ critical_queue.put(SomeJob, {})
15
+
16
+ reserver = Qmore::Reservers::Default.new([critical_queue, high_queue])
17
+
18
+ expect(reserver.reserve.queue.name).to eq('critical')
19
+ expect(reserver.reserve.queue.name).to eq('high')
20
+ end
21
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ describe 'Reservers::Delegating' do
4
+ before(:each) do
5
+ Redis.connect(:port => 6380).flushall
6
+ Qmore.client.redis.flushall
7
+ Qmore.configuration = Qmore::Configuration.new
8
+ end
9
+
10
+ it 'should implement #queues' do
11
+ qless1 = Qless::Client.new(:redis => Redis.connect(:port => 6379))
12
+ qless2 = Qless::Client.new(:redis => Redis.connect(:port => 6380))
13
+ queue_a = qless1.queues["a"]
14
+ queue_b = qless2.queues["b"]
15
+
16
+ job1 = queue_a.put(SomeJob, {})
17
+ job2 = queue_b.put(SomeJob, {})
18
+
19
+ expect(queue_a.length).to eq(1)
20
+ expect(queue_b.length).to eq(1)
21
+
22
+ reservers = []
23
+ reservers << Qmore::Reservers::Default.new([queue_a])
24
+ reservers << Qmore::Reservers::Default.new([queue_b])
25
+
26
+ reserver = Qmore::Reservers::Delegating.new(reservers)
27
+
28
+ queues = reserver.queues.to_a
29
+ expect(queues).to include(queue_a)
30
+ expect(queues).to include(queue_b)
31
+ end
32
+
33
+ it 'can delegate to multiple reservers' do
34
+ qless1 = Qless::Client.new(:redis => Redis.connect(:port => 6379))
35
+ qless2 = Qless::Client.new(:redis => Redis.connect(:port => 6380))
36
+ queue_a = qless1.queues["a"]
37
+ queue_b = qless2.queues["b"]
38
+
39
+ job1 = queue_a.put(SomeJob, {})
40
+ job2 = queue_b.put(SomeJob, {})
41
+
42
+ expect(queue_a.length).to eq(1)
43
+ expect(queue_b.length).to eq(1)
44
+
45
+ reserver1 = Qmore::Reservers::Default.new([queue_a])
46
+ reserver2 = Qmore::Reservers::Default.new([queue_b])
47
+
48
+ reserver = Qmore::Reservers::Delegating.new([reserver1, reserver2])
49
+ expect(reserver.reserve.queue.name).to eq('a')
50
+ expect(reserver.reserve.queue.name).to eq('b')
51
+ end
52
+
53
+ context 'with ordering' do
54
+ it 'should work with ordering strategy' do
55
+ qless1 = Qless::Client.new(:redis => Redis.connect(:port => 6379))
56
+ qless2 = Qless::Client.new(:redis => Redis.connect(:port => 6380))
57
+ queue_a = qless1.queues["a"]
58
+ queue_b = qless2.queues["b"]
59
+
60
+ job1 = queue_a.put(SomeJob, {})
61
+ job2 = queue_b.put(SomeJob, {})
62
+
63
+ expect(queue_a.length).to eq(1)
64
+ expect(queue_b.length).to eq(1)
65
+
66
+ reservers = []
67
+ reservers << Qmore::Reservers::Default.new([queue_a])
68
+ reservers << Qmore::Reservers::Default.new([queue_b])
69
+
70
+ reservers = Qmore::Reservers::Strategies::Ordering.shuffled(reservers)
71
+ reserver = Qmore::Reservers::Delegating.new(reservers)
72
+
73
+ expected = ['a', 'b']
74
+ expected.delete(reserver.reserve.queue.name)
75
+ expected.delete(reserver.reserve.queue.name)
76
+ expect(expected).to be_empty
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,62 @@
1
+ require "spec_helper"
2
+
3
+ describe "Reservers::Strategies::Filtering" do
4
+ before(:each) do
5
+ Qmore.client.redis.flushall
6
+ Qmore.configuration = Qmore::Configuration.new
7
+ end
8
+
9
+ context "default qless filtering behavior" do
10
+ it "can filter multiple queues" do
11
+ high_queue = Qmore.client.queues['high']
12
+ critical_queue = Qmore.client.queues['critical']
13
+
14
+ high_queue.put(SomeJob, {})
15
+ critical_queue.put(SomeJob, {})
16
+
17
+ queues = [high_queue, critical_queue]
18
+ filter = Qmore::Reservers::Strategies::Filtering.default(queues, ["*"])
19
+
20
+ queues = filter.collect(&:name)
21
+ expect(queues).to include('critical')
22
+ expect(queues).to include('high')
23
+ end
24
+
25
+ it "should only return matching queues" do
26
+ high_queue = Qmore.client.queues['high']
27
+ critical_queue = Qmore.client.queues['critical']
28
+
29
+ high_queue.put(SomeJob, {})
30
+ critical_queue.put(SomeJob, {})
31
+
32
+ queues = [high_queue, critical_queue]
33
+ filter = Qmore::Reservers::Strategies::Filtering.default(queues, ['critical'])
34
+
35
+ queues = filter.collect(&:name)
36
+ expect(queues).to include('critical')
37
+ expect(queues).to_not include('high')
38
+ end
39
+
40
+ it "handles priorities" do
41
+ Qmore.configuration.priority_buckets = [{'pattern' => 'foo*', 'fairly' => false},
42
+ {'pattern' => 'default', 'fairly' => false},
43
+ {'pattern' => 'bar', 'fairly' => true}]
44
+
45
+ queues = []
46
+ ['other', 'blah', 'foobie', 'bar', 'foo'].each do |q|
47
+ queue = Qmore.client.queues[q]
48
+ queue.put(SomeJob, {})
49
+ expect(queue.length).to be(1)
50
+ queues << queue
51
+ end
52
+
53
+ filter = Qmore::Reservers::Strategies::Filtering.default(queues, ['*', '!blah'])
54
+
55
+ expect(filter.next.name).to eq('foo')
56
+ expect(filter.next.name).to eq('foobie')
57
+ expect(filter.next.name).to eq('other')
58
+ expect(filter.next.name).to eq('bar')
59
+ expect { filter.next }.to raise_error(StopIteration)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe "Reservers::Strategies::Ordering" do
4
+ before(:each) do
5
+ Qmore.client.redis.flushall
6
+ Qmore.configuration = Qmore::Configuration.new
7
+ end
8
+
9
+ context 'shuffled order' do
10
+ it 'should return all items in a shuffled order' do
11
+ input = [1,2,3,4,5,6,7,8]
12
+ ordering = Qmore::Reservers::Strategies::Ordering.shuffled(input)
13
+
14
+ round1 = ordering.to_a
15
+ round2 = ordering.to_a
16
+
17
+ expect(round1).not_to eq(input)
18
+ expect(round2).not_to eq(input)
19
+ expect(round1).not_to eq(round2)
20
+
21
+ input.each do |element|
22
+ expect(round1).to include(element)
23
+ expect(round2).to include(element)
24
+ end
25
+ end
26
+ end
27
+
28
+ context 'sampled subset' do
29
+ it 'should return the specified number of elements' do
30
+ input = [1,2,3,4,5,6,7,8]
31
+ ordering = Qmore::Reservers::Strategies::Ordering.sampled(input, 4)
32
+
33
+ round1 = ordering.to_a
34
+ round2 = ordering.to_a
35
+
36
+ expect(round1.length).to be(4)
37
+ expect(round2.length).to be(4)
38
+
39
+ expect(round1).not_to eq(round2)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,128 @@
1
+ require "spec_helper"
2
+
3
+ describe "Reservers::Strategies::Sources" do
4
+ before(:each) do
5
+ Qmore.client.redis.flushall
6
+ Qmore.configuration = Qmore::Configuration.new
7
+ end
8
+
9
+ context 'direct source' do
10
+ it "does not return queues that have no work available" do
11
+ no_work_queue = Qmore.client.queues['no-work']
12
+ has_work_queue = Qmore.client.queues['has-work']
13
+
14
+ no_work_queue.put(SomeJob, {})
15
+ has_work_queue.put(SomeJob, {})
16
+
17
+ # drain the no work queue
18
+ no_work_queue.pop
19
+
20
+ source = Qmore::Reservers::Strategies::Sources.direct(Qmore.client)
21
+
22
+ queues = source.collect(&:name)
23
+ expect(queues).to include("has-work")
24
+ expect(queues).not_to include("no-work")
25
+ end
26
+
27
+ it "should not ignore queues that have work in scheduled state" do
28
+ work_queue = Qmore.client.queues['work']
29
+ work_queue.put(SomeJob, {}, {:delay => 1600})
30
+
31
+ %w(waiting recurring depends stalled).each do |state|
32
+ expect(work_queue.counts[state]).to be(0)
33
+ end
34
+ expect(work_queue.counts["scheduled"]).to be(1)
35
+
36
+ source = Qmore::Reservers::Strategies::Sources.direct(Qmore.client)
37
+
38
+ queues = source.collect(&:name)
39
+ expect(queues).to include("work")
40
+ end
41
+
42
+ it "should not ignore queues that have work in the depends state" do
43
+ work_queue = Qmore.client.queues['work']
44
+ jid = work_queue.put(SomeJob, {})
45
+ work_queue.put(SomeJob, {}, {:depends => [jid]})
46
+
47
+ work_queue.pop
48
+
49
+ %w(waiting recurring stalled scheduled).each do |state|
50
+ expect(work_queue.counts[state]).to be(0)
51
+ end
52
+ expect(work_queue.counts["depends"]).to be(1)
53
+
54
+ source = Qmore::Reservers::Strategies::Sources.direct(Qmore.client)
55
+
56
+ queues = source.collect(&:name)
57
+ expect(queues).to include("work")
58
+ end
59
+
60
+ it "should not ignore queues that have work in the recurring state" do
61
+ work_queue = Qmore.client.queues['work']
62
+ work_queue.recur(SomeJob, {}, 1000)
63
+
64
+ %w(waiting depends stalled scheduled).each do |state|
65
+ expect(work_queue.counts[state]).to be(0)
66
+ end
67
+ expect(work_queue.counts["recurring"]).to be(1)
68
+
69
+ source = Qmore::Reservers::Strategies::Sources.direct(Qmore.client)
70
+
71
+ queues = source.collect(&:name)
72
+ expect(queues).to include("work")
73
+ end
74
+
75
+ it "should not ignore queues that have work in the waiting state" do
76
+ work_queue = Qmore.client.queues['work']
77
+ work_queue.put(SomeJob, {})
78
+
79
+ %w(recurring depends stalled scheduled).each do |state|
80
+ expect(work_queue.counts[state]).to be(0)
81
+ end
82
+ expect(work_queue.counts["waiting"]).to be(1)
83
+
84
+ source = Qmore::Reservers::Strategies::Sources.direct(Qmore.client)
85
+
86
+ queues = source.collect(&:name)
87
+ expect(queues).to include("work")
88
+ end
89
+ end
90
+
91
+ context 'background source' do
92
+ it 'should return the results from the delegate' do
93
+ work_queue = Qmore.client.queues['work']
94
+ work_queue.put(SomeJob, {})
95
+ source = Qmore::Reservers::Strategies::Sources.direct(Qmore.client)
96
+ source = Qmore::Reservers::Strategies::Sources::Background.new(source, 0.1)
97
+ thread = source.start # Start the update
98
+ source.stop
99
+ thread.join
100
+
101
+ queues = source.collect(&:name)
102
+ expect(queues).to include("work")
103
+ end
104
+
105
+ context 'start' do
106
+ it 'should update from the source' do
107
+ work_queue = Qmore.client.queues['work']
108
+ work_queue.put(SomeJob, {})
109
+ source = Qmore::Reservers::Strategies::Sources.direct(Qmore.client)
110
+ source = Qmore::Reservers::Strategies::Sources::Background.new(source, 0.1)
111
+ thread = source.start # Start the update
112
+
113
+ # Add another queue to the source
114
+ queue = Qmore.client.queues['work-queue']
115
+ queue.put(SomeJob, {})
116
+
117
+ # Sleep long enough for multiple updates to occur
118
+ sleep 0.3
119
+ source.stop
120
+ thread.join
121
+
122
+ queues = source.collect(&:name)
123
+ expect(queues).to include("work")
124
+ expect(queues).to include("work-queue")
125
+ end
126
+ end
127
+ end
128
+ end
@@ -1,6 +1,14 @@
1
1
  require 'rspec'
2
+ RSpec.configure do |config|
3
+ config.expect_with :rspec do |c|
4
+ c.syntax = [:should, :expect]
5
+ end
6
+ config.mock_with :rspec do |c|
7
+ c.syntax = [:should, :expect]
8
+ end
9
+ end
10
+
2
11
  require 'coveralls'
3
- require 'pry'
4
12
  Coveralls.wear!
5
13
 
6
14
  require 'qmore'
metadata CHANGED
@@ -1,144 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qmore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
5
- prerelease:
4
+ version: 0.7.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Matt Conway
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-06-13 00:00:00.000000000 Z
11
+ date: 2014-07-07 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- prerelease: false
16
14
  name: qless
17
- type: :runtime
18
15
  requirement: !ruby/object:Gem::Requirement
19
16
  requirements:
20
- - - ~>
17
+ - - "~>"
21
18
  - !ruby/object:Gem::Version
22
19
  version: '0.9'
23
- none: false
20
+ type: :runtime
21
+ prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
23
  requirements:
26
- - - ~>
24
+ - - "~>"
27
25
  - !ruby/object:Gem::Version
28
26
  version: '0.9'
29
- none: false
30
27
  - !ruby/object:Gem::Dependency
31
- prerelease: false
32
28
  name: multi_json
33
- type: :runtime
34
29
  requirement: !ruby/object:Gem::Requirement
35
30
  requirements:
36
- - - ~>
31
+ - - "~>"
37
32
  - !ruby/object:Gem::Version
38
33
  version: '1.7'
39
- none: false
34
+ type: :runtime
35
+ prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
37
  requirements:
42
- - - ~>
38
+ - - "~>"
43
39
  - !ruby/object:Gem::Version
44
40
  version: '1.7'
45
- none: false
46
41
  - !ruby/object:Gem::Dependency
47
- prerelease: false
48
42
  name: gem_logger
49
- type: :runtime
50
43
  requirement: !ruby/object:Gem::Requirement
51
44
  requirements:
52
- - - ! '>='
45
+ - - ">="
53
46
  - !ruby/object:Gem::Version
54
47
  version: '0'
55
- none: false
48
+ type: :runtime
49
+ prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
51
  requirements:
58
- - - ! '>='
52
+ - - ">="
59
53
  - !ruby/object:Gem::Version
60
54
  version: '0'
61
- none: false
62
55
  - !ruby/object:Gem::Dependency
63
- prerelease: false
64
56
  name: rake
65
- type: :development
66
57
  requirement: !ruby/object:Gem::Requirement
67
58
  requirements:
68
- - - ! '>='
59
+ - - ">="
69
60
  - !ruby/object:Gem::Version
70
61
  version: '0'
71
- none: false
62
+ type: :development
63
+ prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
65
  requirements:
74
- - - ! '>='
66
+ - - ">="
75
67
  - !ruby/object:Gem::Version
76
68
  version: '0'
77
- none: false
78
69
  - !ruby/object:Gem::Dependency
79
- prerelease: false
80
70
  name: rspec
81
- type: :development
82
71
  requirement: !ruby/object:Gem::Requirement
83
72
  requirements:
84
- - - ! '>='
73
+ - - ">="
85
74
  - !ruby/object:Gem::Version
86
75
  version: '0'
87
- none: false
76
+ type: :development
77
+ prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
79
  requirements:
90
- - - ! '>='
80
+ - - ">="
91
81
  - !ruby/object:Gem::Version
92
82
  version: '0'
93
- none: false
94
83
  - !ruby/object:Gem::Dependency
95
- prerelease: false
96
84
  name: rack-test
97
- type: :development
98
85
  requirement: !ruby/object:Gem::Requirement
99
86
  requirements:
100
- - - ! '>='
87
+ - - ">="
101
88
  - !ruby/object:Gem::Version
102
89
  version: '0'
103
- none: false
90
+ type: :development
91
+ prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
93
  requirements:
106
- - - ! '>='
94
+ - - ">="
107
95
  - !ruby/object:Gem::Version
108
96
  version: '0'
109
- none: false
110
97
  - !ruby/object:Gem::Dependency
111
- prerelease: false
112
98
  name: pry
113
- type: :development
114
99
  requirement: !ruby/object:Gem::Requirement
115
100
  requirements:
116
- - - ! '>='
101
+ - - ">="
117
102
  - !ruby/object:Gem::Version
118
103
  version: '0'
119
- none: false
104
+ type: :development
105
+ prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
107
  requirements:
122
- - - ! '>='
108
+ - - ">="
123
109
  - !ruby/object:Gem::Version
124
110
  version: '0'
125
- none: false
126
111
  - !ruby/object:Gem::Dependency
127
- prerelease: false
128
112
  name: orderedhash
129
- type: :development
130
113
  requirement: !ruby/object:Gem::Requirement
131
114
  requirements:
132
- - - ! '>='
115
+ - - ">="
133
116
  - !ruby/object:Gem::Version
134
117
  version: '0'
135
- none: false
118
+ type: :development
119
+ prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
121
  requirements:
138
- - - ! '>='
122
+ - - ">="
139
123
  - !ruby/object:Gem::Version
140
124
  version: '0'
141
- none: false
142
125
  description: Qmore allows one to specify the queues a worker processes by the use
143
126
  of wildcards, negations, or dynamic look up from redis. It also allows one to specify
144
127
  the relative priority between queues (rather than within a single queue). It plugs
@@ -149,8 +132,8 @@ executables: []
149
132
  extensions: []
150
133
  extra_rdoc_files: []
151
134
  files:
152
- - .gitignore
153
- - .travis.yml
135
+ - ".gitignore"
136
+ - ".travis.yml"
154
137
  - CHANGELOG
155
138
  - Gemfile
156
139
  - LICENSE
@@ -161,8 +144,14 @@ files:
161
144
  - lib/qmore.rb
162
145
  - lib/qmore/attributes.rb
163
146
  - lib/qmore/configuration.rb
164
- - lib/qmore/job_reserver.rb
165
147
  - lib/qmore/persistence.rb
148
+ - lib/qmore/reservers.rb
149
+ - lib/qmore/reservers/default.rb
150
+ - lib/qmore/reservers/delegating.rb
151
+ - lib/qmore/reservers/strategies.rb
152
+ - lib/qmore/reservers/strategies/filtering.rb
153
+ - lib/qmore/reservers/strategies/ordering.rb
154
+ - lib/qmore/reservers/strategies/sources.rb
166
155
  - lib/qmore/server.rb
167
156
  - lib/qmore/server/views/dynamicqueues.erb
168
157
  - lib/qmore/server/views/priorities.erb
@@ -170,40 +159,49 @@ files:
170
159
  - qmore.gemspec
171
160
  - spec/attributes_spec.rb
172
161
  - spec/configuration_spec.rb
173
- - spec/job_reserver_spec.rb
174
162
  - spec/persistance_spec.rb
175
163
  - spec/redis/qless01-test.conf
176
164
  - spec/redis/qless02-test.conf
165
+ - spec/reservers/default_spec.rb
166
+ - spec/reservers/delegating_spec.rb
167
+ - spec/reservers/strategies/filtering_spec.rb
168
+ - spec/reservers/strategies/ordering_spec.rb
169
+ - spec/reservers/strategies/sources_spec.rb
177
170
  - spec/server_spec.rb
178
171
  - spec/spec_helper.rb
179
172
  homepage: ''
180
173
  licenses: []
174
+ metadata: {}
181
175
  post_install_message:
182
176
  rdoc_options: []
183
177
  require_paths:
184
178
  - lib
185
179
  required_ruby_version: !ruby/object:Gem::Requirement
186
180
  requirements:
187
- - - ! '>='
181
+ - - ">="
188
182
  - !ruby/object:Gem::Version
189
183
  version: '0'
190
- segments:
191
- - 0
192
- hash: -593121205799441535
193
- none: false
194
184
  required_rubygems_version: !ruby/object:Gem::Requirement
195
185
  requirements:
196
- - - ! '>='
186
+ - - ">="
197
187
  - !ruby/object:Gem::Version
198
188
  version: '0'
199
- segments:
200
- - 0
201
- hash: -593121205799441535
202
- none: false
203
189
  requirements: []
204
190
  rubyforge_project: qmore
205
- rubygems_version: 1.8.23
191
+ rubygems_version: 2.2.2
206
192
  signing_key:
207
- specification_version: 3
193
+ specification_version: 4
208
194
  summary: A qless plugin that gives more control over how queues are processed
209
- test_files: []
195
+ test_files:
196
+ - spec/attributes_spec.rb
197
+ - spec/configuration_spec.rb
198
+ - spec/persistance_spec.rb
199
+ - spec/redis/qless01-test.conf
200
+ - spec/redis/qless02-test.conf
201
+ - spec/reservers/default_spec.rb
202
+ - spec/reservers/delegating_spec.rb
203
+ - spec/reservers/strategies/filtering_spec.rb
204
+ - spec/reservers/strategies/ordering_spec.rb
205
+ - spec/reservers/strategies/sources_spec.rb
206
+ - spec/server_spec.rb
207
+ - spec/spec_helper.rb
@@ -1,63 +0,0 @@
1
- module Qmore
2
- class JobReserver
3
- include Qmore::Attributes
4
- # define queues for Qless worker to invoke.
5
- attr_reader :queues
6
- attr_reader :clients
7
-
8
- def initialize(queues)
9
- @queues = queues
10
- # Pull the regex off of the Qless::Queue#name, we want to keep the same interface
11
- # that Qless reservers use.
12
- @regexes = queues.collect(&:name).uniq
13
- @clients = {}
14
- queues.each do |q|
15
- @clients[q.client] ||= []
16
- @clients[q.client] << q.name
17
- end
18
- end
19
-
20
- def description
21
- @description ||= @regexes.join(', ') + " (qmore)"
22
- end
23
-
24
- def prep_for_work!
25
- # nothing here on purpose
26
- end
27
-
28
- def reserve
29
- self.clients.keys.shuffle.each do |client|
30
- self.extract_queues(client, self.clients[client]).each do |q|
31
- job = q.pop
32
- return job if job
33
- end
34
- end
35
-
36
- nil
37
- end
38
-
39
- # @param [Qless::Client] client - client to pull queues from.
40
- # @param [Array] regexes - array of regular expressions to match
41
- # queues against.
42
- def extract_queues(client, regexes)
43
- # Cache the queues so we don't make multiple calls.
44
- # and remove any queues that don't have work.
45
- actual_queues = client.queues.counts.reject do |queue|
46
- total = %w(waiting recurring depends stalled scheduled).inject(0) { |sum, state| sum += queue[state].to_i }
47
- total == 0
48
- end
49
-
50
- # Grab all the actual queue names from the client.
51
- queue_names = actual_queues.collect {|h| h['name'] }
52
-
53
- # Match the queue names against the regexes provided.
54
- matched_names = expand_queues(regexes, queue_names)
55
-
56
- # Prioritize the queues.
57
- prioritized_names = prioritize_queues(Qmore.configuration.priority_buckets, matched_names)
58
-
59
- # collect the matched queues names in prioritized order.
60
- prioritized_names.collect {|name| client.queues[name] }
61
- end
62
- end
63
- end
@@ -1,195 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe "JobReserver" do
4
- include Qmore::Attributes
5
-
6
- before(:each) do
7
- Qmore.client.redis.flushall
8
- Qmore.configuration = Qmore::Configuration.new
9
- end
10
-
11
- context "multiple qless server environment" do
12
- it "can reserve jobs from regex queue names on multiple clients" do
13
- qless1 = Qless::Client.new(:redis => Redis.connect(:port => 6379))
14
- qless2 = Qless::Client.new(:redis => Redis.connect(:port => 6380))
15
- queue_a = qless1.queues["a"]
16
- queue_b = qless2.queues["b"]
17
- queue_a.put(SomeJob, {})
18
- queue_b.put(SomeJob, {})
19
-
20
- queue_a.length.should == 1
21
- queue_b.length.should == 1
22
-
23
- reserver = Qmore::JobReserver.new([qless1.queues["*"], qless2.queues["*"]])
24
- worker = Qless::Worker.new(reserver, :run_as_single_process => true)
25
- worker.work(0)
26
-
27
- queue_a.length.should == 0
28
- queue_b.length.should == 0
29
- end
30
-
31
- it "shuffles the order of the clients" do
32
- queue = Qmore.client.queues['queue']
33
-
34
- reserver = Qmore::JobReserver.new([queue])
35
- k = reserver.clients.keys
36
- k.should_receive(:shuffle).once.and_return(reserver.clients.keys)
37
- reserver.clients.should_receive(:keys).and_return(k)
38
- reserver.reserve
39
- end
40
- end
41
-
42
- context "basic qless behavior still works" do
43
- it "ignores queues that have no work available" do
44
- no_work_queue = Qmore.client.queues['no-work']
45
- has_work_queue = Qmore.client.queues['has-work']
46
-
47
- no_work_queue.put(SomeJob, {})
48
- has_work_queue.put(SomeJob, {})
49
-
50
- # drain the no work queue
51
- no_work_queue.pop
52
-
53
- reserver = Qmore::JobReserver.new([no_work_queue, has_work_queue])
54
-
55
- queues = reserver.extract_queues(Qmore.client, ["*"]).collect(&:name)
56
- queues.should include("has-work")
57
- queues.should_not include("no-work")
58
- end
59
-
60
- it "should not ignore queues that have work in scheduled state" do
61
- work_queue = Qmore.client.queues['work']
62
- work_queue.put(SomeJob, {}, {:delay => 1600})
63
-
64
- %w(waiting recurring depends stalled).each do |state|
65
- work_queue.counts[state].should equal(0)
66
- end
67
- work_queue.counts["scheduled"].should equal(1)
68
-
69
- reserver = Qmore::JobReserver.new([work_queue])
70
- queues = reserver.extract_queues(Qmore.client, ["*"]).collect(&:name)
71
- queues.should include("work")
72
- end
73
-
74
- it "should not ignore queues that have work in the depends state" do
75
- work_queue = Qmore.client.queues['work']
76
- jid = work_queue.put(SomeJob, {})
77
- work_queue.put(SomeJob, {}, {:depends => [jid]})
78
-
79
- work_queue.pop
80
-
81
- %w(waiting recurring stalled scheduled).each do |state|
82
- work_queue.counts[state].should equal(0)
83
- end
84
- work_queue.counts["depends"].should equal(1)
85
-
86
- reserver = Qmore::JobReserver.new([work_queue])
87
- queues = reserver.extract_queues(Qmore.client, ["*"]).collect(&:name)
88
- queues.should include("work")
89
- end
90
-
91
- it "should not ignore queues that have work in the recurring state" do
92
- work_queue = Qmore.client.queues['work']
93
- work_queue.recur(SomeJob, {}, 1000)
94
-
95
- %w(waiting depends stalled scheduled).each do |state|
96
- work_queue.counts[state].should equal(0)
97
- end
98
- work_queue.counts["recurring"].should equal(1)
99
-
100
- reserver = Qmore::JobReserver.new([work_queue])
101
- queues = reserver.extract_queues(Qmore.client, ["*"]).collect(&:name)
102
- queues.should include("work")
103
- end
104
-
105
- it "should not ignore queues that have work in the waiting state" do
106
- work_queue = Qmore.client.queues['work']
107
- work_queue.put(SomeJob, {})
108
-
109
- %w(recurring depends stalled scheduled).each do |state|
110
- work_queue.counts[state].should equal(0)
111
- end
112
- work_queue.counts["waiting"].should equal(1)
113
-
114
- reserver = Qmore::JobReserver.new([work_queue])
115
- queues = reserver.extract_queues(Qmore.client, ["*"]).collect(&:name)
116
- queues.should include("work")
117
- end
118
-
119
- it "can reserve from multiple queues" do
120
- high_queue = Qmore.client.queues['high']
121
- critical_queue = Qmore.client.queues['critical']
122
-
123
- high_queue.put(SomeJob, {})
124
- critical_queue.put(SomeJob, {})
125
-
126
- reserver = Qmore::JobReserver.new([critical_queue, high_queue])
127
-
128
- reserver.reserve.queue.name.should == 'critical'
129
- reserver.reserve.queue.name.should == 'high'
130
- end
131
-
132
- it "can work on multiple queues" do
133
- high_queue = Qmore.client.queues['high']
134
- critical_queue = Qmore.client.queues['critical']
135
- high_queue.put(SomeJob, {})
136
- critical_queue.put(SomeJob, {})
137
-
138
- high_queue.length.should == 1
139
- critical_queue.length.should == 1
140
-
141
- reserver = Qmore::JobReserver.new([critical_queue, high_queue])
142
-
143
- worker = Qless::Worker.new(reserver,
144
- :run_as_single_process => true)
145
- worker.work(0)
146
-
147
- high_queue.length.should == 0
148
- critical_queue.length.should == 0
149
- end
150
-
151
- it "can work on all queues" do
152
- queues = []
153
- ['high', 'critical', 'blahblah'].each do |q|
154
- queue = Qmore.client.queues[q]
155
- queue.put(SomeJob, {})
156
- queue.length.should == 1
157
- queues << queue
158
- end
159
-
160
- reserver = Qmore::JobReserver.new([Qmore.client.queues['*']])
161
- worker = Qless::Worker.new(reserver,
162
- :run_as_single_process => true)
163
- worker.work(0)
164
-
165
- queues.each do |q|
166
- q.length.should == 0
167
- end
168
- end
169
-
170
- it "handles priorities" do
171
- Qmore.configuration.priority_buckets = [{'pattern' => 'foo*', 'fairly' => false},
172
- {'pattern' => 'default', 'fairly' => false},
173
- {'pattern' => 'bar', 'fairly' => true}]
174
-
175
-
176
- queues = []
177
- ['other', 'blah', 'foobie', 'bar', 'foo'].each do |q|
178
- queue = Qmore.client.queues[q]
179
- queue.put(SomeJob, {})
180
- queue.length.should == 1
181
- queues << queue
182
- end
183
-
184
- reserver = Qmore::JobReserver.new([Qmore.client.queues['*'], Qmore.client.queues['!blah']])
185
-
186
- reserver.reserve.queue.name.should == 'foo'
187
- reserver.reserve.queue.name.should == 'foobie'
188
- reserver.reserve.queue.name.should == 'other'
189
- reserver.reserve.queue.name.should == 'bar'
190
- reserver.reserve.should be_nil
191
- end
192
-
193
- end
194
-
195
- end