queue_bundle 0.0.1
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.
- data/lib/queue_bundle/version.rb +3 -0
- data/lib/queue_bundle.rb +214 -0
- data/test/helper.rb +10 -0
- data/test/test_queue_bundle.rb +236 -0
- metadata +59 -0
data/lib/queue_bundle.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
require "queue_bundle/version"
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
class QueueNotFound < StandardError; end
|
6
|
+
|
7
|
+
class QueueBundle
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
attr_reader :hash_algorithm
|
12
|
+
def initialize(size, options = {})
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@queues = []
|
15
|
+
@size = size
|
16
|
+
@closed = false
|
17
|
+
@hash_algorithm = options[:hash_algorithm] || :simple_hash
|
18
|
+
@key_lookup = options[:key_lookup] || :id
|
19
|
+
1.upto(@size) {|i|
|
20
|
+
@queues << Queue.new
|
21
|
+
}
|
22
|
+
end
|
23
|
+
def is_queue?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
def simple_hash(key)
|
27
|
+
key.hash % @size
|
28
|
+
end
|
29
|
+
def simple_index(key)
|
30
|
+
key
|
31
|
+
end
|
32
|
+
|
33
|
+
# def resize(new_size)
|
34
|
+
#
|
35
|
+
# end
|
36
|
+
|
37
|
+
def closed?
|
38
|
+
@closed
|
39
|
+
end
|
40
|
+
def empty?(index = nil)
|
41
|
+
self.length(index) == 0
|
42
|
+
end
|
43
|
+
def reassign(index)
|
44
|
+
@mutex.synchronize do
|
45
|
+
dest_queue = next_queue(index)
|
46
|
+
if !dest_queue.nil?
|
47
|
+
old_queue = @queues[index]
|
48
|
+
@queues[index] = :reassigned
|
49
|
+
if !old_queue.empty?
|
50
|
+
begin
|
51
|
+
while !old_queue.empty?
|
52
|
+
dest_queue.push(old_queue.pop(true))
|
53
|
+
end
|
54
|
+
rescue ThreadError
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
true
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def close()
|
67
|
+
@closed = true
|
68
|
+
end
|
69
|
+
def push(obj, key = nil)
|
70
|
+
key ||= get_key(obj)
|
71
|
+
queue = get_queue(key)
|
72
|
+
|
73
|
+
if queue.nil?
|
74
|
+
raise QueueNotFound.new("Queue not found for #{key}")
|
75
|
+
else
|
76
|
+
queue.push(obj)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
alias_method :enq, :push
|
80
|
+
def pop(index, non_block = false)
|
81
|
+
queue = @queues[index]
|
82
|
+
|
83
|
+
if queue.nil?
|
84
|
+
raise QueueNotFound.new("Queue not found for #{key}")
|
85
|
+
elsif queue == :reassigned
|
86
|
+
raise QueueNotFound.new("Queue reassigned")
|
87
|
+
else
|
88
|
+
queue.pop(non_block)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
alias_method :deq, :pop
|
92
|
+
def clear(index = nil)
|
93
|
+
if index.nil?
|
94
|
+
active_queues.each {|queue| queue.clear}
|
95
|
+
else
|
96
|
+
queue = @queues[index] #doesn't switch to next queue so that it will error out
|
97
|
+
|
98
|
+
if queue.nil?
|
99
|
+
raise QueueNotFound.new("Queue not found for #{key}")
|
100
|
+
elsif queue == :reassigned
|
101
|
+
raise QueueNotFound.new("Queue reassigned")
|
102
|
+
else
|
103
|
+
queue.clear()
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
def num_waiting(index = nil)
|
108
|
+
if index.nil?
|
109
|
+
active_queues.map {|queue| queue.num_waiting}.inject{|sum,x| sum + x }
|
110
|
+
else
|
111
|
+
queue = @queues[index]
|
112
|
+
|
113
|
+
if queue.nil?
|
114
|
+
raise QueueNotFound.new("Queue not found for #{key}")
|
115
|
+
elsif queue == :reassigned
|
116
|
+
raise QueueNotFound.new("Queue reassigned")
|
117
|
+
else
|
118
|
+
queue.num_waiting
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
def empty?(index = nil)
|
123
|
+
if index.nil?
|
124
|
+
active_queues.inject{|total,y| (!total || !y) ? false : true } # if it sees a false then it is not empty
|
125
|
+
else
|
126
|
+
queue = @queues[index]
|
127
|
+
|
128
|
+
if queue.nil?
|
129
|
+
raise QueueNotFound.new("Queue not found for #{key}")
|
130
|
+
elsif queue == :reassigned
|
131
|
+
raise QueueNotFound.new("Queue reassigned")
|
132
|
+
else
|
133
|
+
queue.empty?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def length(index = nil)
|
139
|
+
if index.nil?
|
140
|
+
active_queues.map {|queue| queue.length}.inject{|sum,x| sum + x }
|
141
|
+
else
|
142
|
+
queue = @queues[index]
|
143
|
+
|
144
|
+
if queue.nil?
|
145
|
+
raise QueueNotFound.new("Queue not found for #{key}")
|
146
|
+
elsif queue == :reassigned
|
147
|
+
raise QueueNotFound.new("Queue reassigned")
|
148
|
+
else
|
149
|
+
queue.length
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
alias_method :size, :length
|
155
|
+
|
156
|
+
def lengths
|
157
|
+
length_hash = {}
|
158
|
+
active_queues.each_index {|queue_index|
|
159
|
+
length_hash[queue_index] = @queues[queue_index].length
|
160
|
+
}
|
161
|
+
length_hash
|
162
|
+
end
|
163
|
+
alias_method :sizes, :lengths
|
164
|
+
private
|
165
|
+
|
166
|
+
def get_key(obj)
|
167
|
+
key = nil
|
168
|
+
if @key_lookup.kind_of?(Proc)
|
169
|
+
key = @key_lookup.call obj
|
170
|
+
elsif @key_lookup.kind_of?(Symbol)
|
171
|
+
if obj.kind_of?(Hash)
|
172
|
+
key = obj[@key_lookup]
|
173
|
+
else
|
174
|
+
key = obj.send(@key_lookup)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
key
|
178
|
+
end
|
179
|
+
def get_queue(key)
|
180
|
+
queue_index = nil
|
181
|
+
if @hash_algorithm.kind_of?(Proc)
|
182
|
+
queue_index = @hash_algorithm.call key
|
183
|
+
elsif @hash_algorithm.kind_of?(Symbol)
|
184
|
+
queue_index = self.send(@hash_algorithm, key)
|
185
|
+
end
|
186
|
+
queue = @queues[queue_index]
|
187
|
+
if queue == :reassigned
|
188
|
+
queue = next_queue(queue_index)
|
189
|
+
end
|
190
|
+
|
191
|
+
queue
|
192
|
+
end
|
193
|
+
def active_queues
|
194
|
+
@queues.select {|queue| queue != :reassigned }
|
195
|
+
end
|
196
|
+
def next_queue(index)
|
197
|
+
current_index = index
|
198
|
+
while n = next_index(current_index)
|
199
|
+
break if n == index
|
200
|
+
if @queues[n] != :reassigned
|
201
|
+
return @queues[n]
|
202
|
+
end
|
203
|
+
current_index = n
|
204
|
+
end
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
def next_index(index)
|
208
|
+
n = index + 1
|
209
|
+
if n >= @size
|
210
|
+
n = 0
|
211
|
+
end
|
212
|
+
n
|
213
|
+
end
|
214
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'helper'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
class TestQueueBundle < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_simple_index
|
9
|
+
queue = QueueBundle.new(5, :hash_algorithm => :simple_index)
|
10
|
+
assert_equal :simple_index, queue.hash_algorithm
|
11
|
+
assert_raise(QueueNotFound) do
|
12
|
+
queue.push(1, 50)
|
13
|
+
end
|
14
|
+
0.upto(4) {|i|
|
15
|
+
queue.push(i, i)
|
16
|
+
}
|
17
|
+
assert_equal 5, queue.length
|
18
|
+
0.upto(4) {|i|
|
19
|
+
assert_equal 1, queue.length(i)
|
20
|
+
}
|
21
|
+
0.upto(4) {|i|
|
22
|
+
assert_equal i, queue.pop(i)
|
23
|
+
assert_equal 0, queue.length(i)
|
24
|
+
assert_raise(ThreadError) do
|
25
|
+
queue.pop(i, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
}
|
29
|
+
assert_equal 0, queue.length
|
30
|
+
assert_equal false, queue.closed?
|
31
|
+
queue.close
|
32
|
+
assert_equal true, queue.closed?
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_simple_hash
|
36
|
+
queue = QueueBundle.new(5, :hash_algorithm => :simple_hash)
|
37
|
+
assert_equal :simple_hash, queue.hash_algorithm
|
38
|
+
|
39
|
+
0.upto(4) {|i|
|
40
|
+
queue.push(i, i)
|
41
|
+
}
|
42
|
+
|
43
|
+
assert_equal 5, queue.length
|
44
|
+
results = []
|
45
|
+
0.upto(4) {|i|
|
46
|
+
1.upto(queue.length(i)) {
|
47
|
+
results << queue.pop(i)
|
48
|
+
}
|
49
|
+
}
|
50
|
+
assert_equal [0,1,2,3,4], results.sort
|
51
|
+
assert_equal 0, queue.length
|
52
|
+
assert_equal false, queue.closed?
|
53
|
+
queue.close
|
54
|
+
assert_equal true, queue.closed?
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_simple_index_with_hash_key
|
58
|
+
queue = QueueBundle.new(5, :hash_algorithm => :simple_index)
|
59
|
+
assert_equal :simple_index, queue.hash_algorithm
|
60
|
+
assert_raise(QueueNotFound) do
|
61
|
+
queue.push(1, 50)
|
62
|
+
end
|
63
|
+
0.upto(4) {|i|
|
64
|
+
queue.push({:id => i})
|
65
|
+
}
|
66
|
+
assert_equal 5, queue.length
|
67
|
+
0.upto(4) {|i|
|
68
|
+
assert_equal 1, queue.length(i)
|
69
|
+
}
|
70
|
+
0.upto(4) {|i|
|
71
|
+
assert_equal i, queue.pop(i)[:id]
|
72
|
+
assert_equal 0, queue.length(i)
|
73
|
+
assert_raise(ThreadError) do
|
74
|
+
queue.pop(i, true)
|
75
|
+
end
|
76
|
+
|
77
|
+
}
|
78
|
+
assert_equal 0, queue.length
|
79
|
+
assert_equal false, queue.closed?
|
80
|
+
queue.close
|
81
|
+
assert_equal true, queue.closed?
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_simple_index_with_hash_alt_key
|
85
|
+
queue = QueueBundle.new(5, :hash_algorithm => :simple_index, :key_lookup => :alt)
|
86
|
+
assert_equal :simple_index, queue.hash_algorithm
|
87
|
+
assert_raise(QueueNotFound) do
|
88
|
+
queue.push(1, 50)
|
89
|
+
end
|
90
|
+
0.upto(4) {|i|
|
91
|
+
queue.push({:alt => i})
|
92
|
+
}
|
93
|
+
assert_equal 5, queue.length
|
94
|
+
0.upto(4) {|i|
|
95
|
+
assert_equal 1, queue.length(i)
|
96
|
+
}
|
97
|
+
0.upto(4) {|i|
|
98
|
+
assert_equal i, queue.pop(i)[:alt]
|
99
|
+
assert_equal 0, queue.length(i)
|
100
|
+
assert_raise(ThreadError) do
|
101
|
+
queue.pop(i, true)
|
102
|
+
end
|
103
|
+
|
104
|
+
}
|
105
|
+
assert_equal 0, queue.length
|
106
|
+
assert_equal false, queue.closed?
|
107
|
+
queue.close
|
108
|
+
assert_equal true, queue.closed?
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_simple_index_with_obj_alt_key
|
112
|
+
queue = QueueBundle.new(5, :hash_algorithm => :simple_index, :key_lookup => :alt)
|
113
|
+
assert_equal :simple_index, queue.hash_algorithm
|
114
|
+
assert_raise(QueueNotFound) do
|
115
|
+
queue.push(1, 50)
|
116
|
+
end
|
117
|
+
0.upto(4) {|i|
|
118
|
+
queue.push(OpenStruct.new({:alt => i}))
|
119
|
+
}
|
120
|
+
assert_equal 5, queue.length
|
121
|
+
0.upto(4) {|i|
|
122
|
+
assert_equal 1, queue.length(i)
|
123
|
+
}
|
124
|
+
0.upto(4) {|i|
|
125
|
+
assert_equal i, queue.pop(i).alt
|
126
|
+
assert_equal 0, queue.length(i)
|
127
|
+
assert_raise(ThreadError) do
|
128
|
+
queue.pop(i, true)
|
129
|
+
end
|
130
|
+
|
131
|
+
}
|
132
|
+
assert_equal 0, queue.length
|
133
|
+
assert_equal false, queue.closed?
|
134
|
+
queue.close
|
135
|
+
assert_equal true, queue.closed?
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_simple_index_with_reassign
|
139
|
+
queue = QueueBundle.new(5, :hash_algorithm => :simple_index)
|
140
|
+
assert_equal :simple_index, queue.hash_algorithm
|
141
|
+
assert_raise(QueueNotFound) do
|
142
|
+
queue.push(1, 50)
|
143
|
+
end
|
144
|
+
0.upto(4) {|i|
|
145
|
+
queue.push(i, i)
|
146
|
+
}
|
147
|
+
assert_equal 5, queue.length
|
148
|
+
0.upto(4) {|i|
|
149
|
+
assert_equal 1, queue.length(i)
|
150
|
+
}
|
151
|
+
assert_equal true, queue.reassign(4)
|
152
|
+
assert_equal 2, queue.length(0) #wrapped
|
153
|
+
1.upto(3) {|i|
|
154
|
+
assert_equal 1, queue.length(i)
|
155
|
+
}
|
156
|
+
assert_raise(QueueNotFound) do
|
157
|
+
queue.pop(4, true)
|
158
|
+
end
|
159
|
+
assert_equal 0, queue.pop(0)
|
160
|
+
assert_equal 4, queue.pop(0)
|
161
|
+
|
162
|
+
1.upto(3) {|i|
|
163
|
+
assert_equal i, queue.pop(i)
|
164
|
+
assert_equal 0, queue.length(i)
|
165
|
+
assert_raise(ThreadError) do
|
166
|
+
queue.pop(i, true)
|
167
|
+
end
|
168
|
+
|
169
|
+
}
|
170
|
+
|
171
|
+
assert_equal 0, queue.length
|
172
|
+
queue.push(4,4)
|
173
|
+
assert_equal 1, queue.length(0)
|
174
|
+
|
175
|
+
assert_equal false, queue.closed?
|
176
|
+
queue.close
|
177
|
+
assert_equal true, queue.closed?
|
178
|
+
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_simple_index_with_reassign_all
|
183
|
+
queue = QueueBundle.new(5, :hash_algorithm => :simple_index)
|
184
|
+
assert_equal :simple_index, queue.hash_algorithm
|
185
|
+
assert_raise(QueueNotFound) do
|
186
|
+
queue.push(1, 50)
|
187
|
+
end
|
188
|
+
0.upto(4) {|i|
|
189
|
+
queue.push(i, i)
|
190
|
+
}
|
191
|
+
assert_equal true, queue.reassign(3)
|
192
|
+
assert_equal 2, queue.size(4)
|
193
|
+
assert_equal true, queue.reassign(4)
|
194
|
+
assert_equal 3, queue.size(0)
|
195
|
+
assert_equal true, queue.reassign(0)
|
196
|
+
assert_equal 4, queue.size(1)
|
197
|
+
assert_equal true, queue.reassign(1)
|
198
|
+
assert_equal 5, queue.size(2)
|
199
|
+
assert_equal false, queue.reassign(2)
|
200
|
+
|
201
|
+
|
202
|
+
assert_equal 2, queue.pop(2)
|
203
|
+
assert_equal 1, queue.pop(2)
|
204
|
+
assert_equal 0, queue.pop(2)
|
205
|
+
assert_equal 4, queue.pop(2)
|
206
|
+
assert_equal 3, queue.pop(2)
|
207
|
+
|
208
|
+
|
209
|
+
end
|
210
|
+
def test_doc_example
|
211
|
+
queue = QueueBundle.new(5, :key_lookup => :id)
|
212
|
+
threads = []
|
213
|
+
0.upto(4).each {|worker_id|
|
214
|
+
threads << Thread.new do
|
215
|
+
loop do
|
216
|
+
break if queue.closed? && queue.empty?(worker_id)
|
217
|
+
work = queue.pop(worker_id) #in this case this would be blocking
|
218
|
+
end
|
219
|
+
end
|
220
|
+
}
|
221
|
+
0.upto(50).each {|work_id|
|
222
|
+
queue.push({id: work_id, name: "task"})
|
223
|
+
}
|
224
|
+
|
225
|
+
queue.close
|
226
|
+
|
227
|
+
threads.compact.each do |t|
|
228
|
+
begin
|
229
|
+
t.join
|
230
|
+
rescue Interrupt
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: queue_bundle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pete Brumm
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-12 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Allows queue work to be distributed to seperate threads that need a consistent end point. Allows you to provide a hashing algorithm for which thread handles the work.
|
17
|
+
email:
|
18
|
+
- pbrumm@edgenet.com
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- lib/queue_bundle.rb
|
27
|
+
- lib/queue_bundle/version.rb
|
28
|
+
- test/helper.rb
|
29
|
+
- test/test_queue_bundle.rb
|
30
|
+
homepage: ""
|
31
|
+
licenses: []
|
32
|
+
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
requirements: []
|
51
|
+
|
52
|
+
rubyforge_project: queue_bundle
|
53
|
+
rubygems_version: 1.7.2
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Provides a way to split queue put's into multiple seperate queue pulls
|
57
|
+
test_files:
|
58
|
+
- test/helper.rb
|
59
|
+
- test/test_queue_bundle.rb
|