super_queue 0.0.7

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.
Files changed (2) hide show
  1. data/lib/super_queue.rb +307 -0
  2. metadata +47 -0
@@ -0,0 +1,307 @@
1
+ require 'fog'
2
+ require 'base64'
3
+ require 'socket'
4
+ require 'digest/md5'
5
+
6
+ class SuperQueue
7
+
8
+ def self.mock!
9
+ @@mock = true
10
+ Fog.mock!
11
+ end
12
+
13
+ def self.mocking?
14
+ defined?(@@mock) && @@mock
15
+ end
16
+
17
+ def initialize(opts)
18
+ check_opts(opts)
19
+ opts[:localize_queue] = true unless opts.has_key? :localized_queue
20
+ opts[:buffer_size] = 100 unless opts.has_key? :buffer_size
21
+
22
+ @localize_queue = opts[:localize_queue]
23
+ @queue_name = generate_queue_name(opts)
24
+ initialize_sqs(opts)
25
+
26
+ @waiting = []
27
+ @waiting.taint
28
+ self.taint
29
+ @mutex = Mutex.new
30
+ @in_buffer = SizedQueue.new(opts[:buffer_size])
31
+ @out_buffer = SizedQueue.new(opts[:buffer_size])
32
+ @deletion_queue = Queue.new
33
+ @mock_length = 0 if SuperQueue.mocking?
34
+
35
+ @sqs_head_tracker = Thread.new { poll_sqs_head }
36
+ @sqs_tail_tracker = Thread.new { poll_sqs_tail }
37
+ @garbage_collector = Thread.new { collect_garbage }
38
+ end
39
+
40
+ def push(p)
41
+ @mutex.synchronize {
42
+ @in_buffer.push p
43
+ begin
44
+ t = @waiting.shift
45
+ t.wakeup if t
46
+ rescue ThreadError
47
+ retry
48
+ end
49
+ }
50
+ end
51
+
52
+ def pop(non_block=false)
53
+ @mutex.synchronize {
54
+ while true
55
+ if @out_buffer.empty?
56
+ if fill_out_buffer_from_sqs_queue || fill_out_buffer_from_in_buffer
57
+ return pop_out_buffer(non_block)
58
+ else
59
+ raise ThreadError, "queue empty" if non_block
60
+ @waiting.push Thread.current
61
+ @mutex.sleep
62
+ end
63
+ else
64
+ return pop_out_buffer(non_block)
65
+ end
66
+ end
67
+ }
68
+ end
69
+
70
+ def length
71
+ @mutex.synchronize {
72
+ return sqs_length + @in_buffer.size + @out_buffer.size
73
+ }
74
+ end
75
+
76
+ def empty?
77
+ self.length == 0
78
+ end
79
+
80
+ def num_waiting
81
+ @waiting.size
82
+ end
83
+
84
+ def clear
85
+ begin
86
+ self.pop(true)
87
+ rescue ThreadError
88
+ retry unless self.empty?
89
+ end until self.empty?
90
+ end
91
+
92
+ def shutdown
93
+ @sqs_head_tracker.terminate
94
+ while !@in_buffer.empty? do
95
+ @sqs.delete_message(q_url, @in_buffer.pop)
96
+ end
97
+ @sqs_tail_tracker.terminate
98
+ @garbage_collector.terminate
99
+ while !@deletion_queue.empty?
100
+ @sqs.delete_message(q_url, @deletion_queue.pop)
101
+ end
102
+ end
103
+
104
+ def destroy
105
+ @sqs_head_tracker.terminate
106
+ @sqs_tail_tracker.terminate
107
+ @garbage_collector.terminate
108
+ delete_queue
109
+ end
110
+
111
+ alias enq push
112
+ alias << push
113
+
114
+ alias deq pop
115
+ alias shift pop
116
+
117
+ alias size length
118
+
119
+ def url
120
+ q_url
121
+ end
122
+
123
+ def name
124
+ queue_name
125
+ end
126
+
127
+ def localized?
128
+ !!@localize_queue
129
+ end
130
+
131
+ private
132
+
133
+ def check_opts(opts)
134
+ raise "Options can't be nil!" if opts.nil?
135
+ raise "Minimun :buffer_size is 5." if opts[:buffer_size] && (opts[:buffer_size] < 5)
136
+ raise "AWS credentials :aws_access_key_id and :aws_secret_access_key required!" unless opts[:aws_access_key_id] && opts[:aws_secret_access_key]
137
+ raise "Visbility timeout must be an integer (in seconds)!" if opts[:visibility_timeout] && !opts[:visibility_timeout].is_a?(Integer)
138
+ end
139
+
140
+ def initialize_sqs(opts)
141
+ create_sqs_connection(opts)
142
+ create_sqs_queue(opts)
143
+ check_for_queue_creation_success
144
+ @sqs.set_queue_attributes(q_url, "VisibilityTimeout", opts[:visibility_timeout]) if opts[:visibility_timeout]
145
+ end
146
+
147
+ def create_sqs_connection(opts)
148
+ aws_options = {
149
+ :aws_access_key_id => opts[:aws_access_key_id],
150
+ :aws_secret_access_key => opts[:aws_secret_access_key]
151
+ }
152
+ begin
153
+ @sqs = Fog::AWS::SQS.new(aws_options)
154
+ rescue Exception => e
155
+ raise e
156
+ end
157
+ end
158
+
159
+ def create_sqs_queue(opts)
160
+ @sqs_queue = @sqs.create_queue(queue_name)
161
+ if opts[:replace_existing_queue] && (sqs_length > 0)
162
+ delete_queue
163
+ puts "Waiting 60s to create new queue..."
164
+ sleep 62 # You must wait 60s after deleting a q to create one with the same name
165
+ if opts[:visibility_timeout]
166
+ @sqs_queue = @sqs.create_queue(queue_name, {"DefaultVisibilityTimeout" => opts[:visibility_timeout]})
167
+ else
168
+ @sqs_queue = @sqs.create_queue(queue_name)
169
+ end
170
+ end
171
+ end
172
+
173
+ def check_for_queue_creation_success
174
+ retries = 0
175
+ while q_url.nil? && (retries < 5)
176
+ retries += 1
177
+ sleep 1
178
+ end
179
+ raise "Couldn't create queue #{queue_name}, or delete existing queue by this name." if q_url.nil?
180
+ end
181
+
182
+ def send_message_to_queue(p)
183
+ payload = is_a_link?(p) ? p : Base64.encode64(Marshal.dump(p))
184
+ @sqs.send_message(q_url, payload)
185
+ @mock_length += 1 if SuperQueue.mocking?
186
+ end
187
+
188
+ def get_message_from_queue
189
+ message = @sqs.receive_message(q_url)
190
+ return nil if message.body.nil? || message.body['Message'].first.nil?
191
+ handle = message.body['Message'].first['ReceiptHandle']
192
+ ser_obj = message.body['Message'].first['Body']
193
+ return nil if ser_obj.nil? || ser_obj.empty?
194
+ @mock_length -= 1 if SuperQueue.mocking?
195
+ return {:handle => handle, :payload => ser_obj} if is_a_link?(ser_obj)
196
+ { :handle => handle, :payload => Marshal.load(Base64.decode64(ser_obj)) }
197
+ end
198
+
199
+ def q_url
200
+ return @q_url if @q_url
201
+ @q_url = @sqs_queue.body['QueueUrl']
202
+ @q_url
203
+ end
204
+
205
+ def is_a_link?(s)
206
+ return false unless s.is_a? String
207
+ (s[0..6] == "http://") || (s[0..7] == "https://")
208
+ end
209
+
210
+ def delete_queue
211
+ @sqs.delete_queue(q_url)
212
+ end
213
+
214
+ def generate_queue_name(opts)
215
+ q_name = opts[:name] || random_name
216
+ if opts[:namespace] && opts[:localize_queue]
217
+ "#{@namespace}-#{Digest::MD5.hexdigest(local_ip)}-#{q_name}"
218
+ elsif opts[:namespace]
219
+ "#{@namespace}-#{q_name}"
220
+ elsif opts[:localize_queue]
221
+ "#{Digest::MD5.hexdigest(local_ip)}-#{q_name}"
222
+ else
223
+ q_name
224
+ end
225
+ end
226
+
227
+ def random_name
228
+ o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten
229
+ (0...15).map{ o[rand(o.length)] }.join
230
+ end
231
+
232
+ def sqs_length
233
+ return @mock_length if SuperQueue.mocking?
234
+ body = @sqs.get_queue_attributes(q_url, "ApproximateNumberOfMessages").body
235
+ begin
236
+ retval = 0
237
+ if body
238
+ attrs = body["Attributes"]
239
+ if attrs
240
+ retval = attrs["ApproximateNumberOfMessages"]
241
+ end
242
+ end
243
+ end until !retval.nil?
244
+ retval
245
+ end
246
+
247
+ def local_ip
248
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
249
+ return "127.0.0.1" if SuperQueue.mocking?
250
+ UDPSocket.open do |s|
251
+ s.connect '64.233.187.99', 1
252
+ s.addr.last
253
+ end
254
+ ensure
255
+ Socket.do_not_reverse_lookup = orig
256
+ end
257
+
258
+ def poll_sqs_head
259
+ loop { send_message_to_queue(@in_buffer.pop) }
260
+ end
261
+
262
+ def poll_sqs_tail
263
+ loop do
264
+ nil_count = 0
265
+ while (@out_buffer.size < @out_buffer.max) && (nil_count < 5) # If you get nil 5 times in a row, SQS is probably empty
266
+ m = get_message_from_queue
267
+ if m.nil?
268
+ nil_count += 1
269
+ else
270
+ @out_buffer.push m
271
+ nil_count = 0
272
+ end
273
+ end unless sqs_length == 0
274
+ sleep
275
+ end
276
+ end
277
+
278
+ def collect_garbage
279
+ loop { @sqs.delete_message(q_url, @deletion_queue.pop) }
280
+ end
281
+
282
+ def fill_out_buffer_from_sqs_queue
283
+ @sqs_tail_tracker.wakeup if @sqs_tail_tracker.stop?
284
+ count = 0
285
+ while @out_buffer.empty? && count != 5
286
+ sleep 1
287
+ count += 1
288
+ end
289
+ end
290
+
291
+ def fill_out_buffer_from_in_buffer
292
+ while (@out_buffer.size < @out_buffer.max) && !@in_buffer.empty?
293
+ @out_buffer.push @in_buffer.pop
294
+ end
295
+ !@out_buffer.empty?
296
+ end
297
+
298
+ def pop_out_buffer(non_block)
299
+ m = @out_buffer.pop(non_block)
300
+ @deletion_queue << m[:handle]
301
+ m[:payload]
302
+ end
303
+
304
+ def queue_name
305
+ @queue_name
306
+ end
307
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: super_queue
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jon Stokes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2010-02-09 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A an SQS-backed queue.
15
+ email: jon@jonstokes.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/super_queue.rb
21
+ homepage: http://rubygems.org/gems/superqueue
22
+ licenses: []
23
+ post_install_message:
24
+ rdoc_options: []
25
+ require_paths:
26
+ - lib
27
+ required_ruby_version: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ! '>='
30
+ - !ruby/object:Gem::Version
31
+ version: !binary |-
32
+ MA==
33
+ none: false
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: !binary |-
39
+ MA==
40
+ none: false
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 1.8.24
44
+ signing_key:
45
+ specification_version: 3
46
+ summary: An SQS-backed queue structure for ruby that works just like a normal queue, except it's essentially infinite and can use very little memory.
47
+ test_files: []