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.
@@ -0,0 +1,3 @@
1
+ class QueueBundle
2
+ VERSION = "0.0.1"
3
+ end
@@ -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,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+
7
+ require 'queue_bundle'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -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