super_queue 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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: []