gorgon 0.5.0.rc1 → 0.6.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +2 -4
- data/gorgon.gemspec +0 -1
- data/lib/gorgon/amqp_service.rb +5 -5
- data/lib/gorgon/gem_command_handler.rb +2 -1
- data/lib/gorgon/listener.rb +7 -5
- data/lib/gorgon/originator_protocol.rb +1 -0
- data/lib/gorgon/version.rb +1 -1
- data/lib/gorgon/worker_manager.rb +5 -2
- data/lib/gorgon_amq-protocol/.gitignore +15 -0
- data/lib/gorgon_amq-protocol/.gitmodules +3 -0
- data/lib/gorgon_amq-protocol/.rspec +3 -0
- data/lib/gorgon_amq-protocol/.travis.yml +19 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/bit_set.rb +82 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/endianness.rb +15 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/int_allocator.rb +96 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/pack.rb +53 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol.rb +4 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/client.rb +2322 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/constants.rb +22 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/exceptions.rb +60 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/float_32bit.rb +14 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/frame.rb +210 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table.rb +142 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_decoder.rb +190 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_encoder.rb +123 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/type_constants.rb +26 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/version.rb +5 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/settings.rb +114 -0
- data/lib/gorgon_amq-protocol/lib/gorgon_amq/uri.rb +37 -0
- data/lib/gorgon_bunny/lib/gorgon_amq/protocol/extensions.rb +16 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny.rb +89 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/credentials_encoder.rb +55 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/external_mechanism_encoder.rb +27 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/authentication/plain_mechanism_encoder.rb +19 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/channel.rb +1875 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/channel_id_allocator.rb +80 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/compatibility.rb +24 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/atomic_fixnum.rb +74 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/condition.rb +66 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/continuation_queue.rb +41 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/linked_continuation_queue.rb +61 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer.rb +123 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_tag_generator.rb +23 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/consumer_work_pool.rb +94 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/delivery_info.rb +93 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/exceptions.rb +236 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/exchange.rb +271 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/framing.rb +56 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/heartbeat_sender.rb +70 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/message_properties.rb +119 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/queue.rb +387 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/reader_loop.rb +116 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/return_info.rb +74 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/session.rb +1044 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/socket.rb +83 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/ssl_socket.rb +57 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/system_timer.rb +20 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/test_kit.rb +27 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/timeout.rb +18 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/transport.rb +398 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/version.rb +6 -0
- data/lib/gorgon_bunny/lib/gorgon_bunny/versioned_delivery_tag.rb +28 -0
- data/spec/crash_reporter_spec.rb +1 -1
- data/spec/gem_command_handler_spec.rb +2 -2
- data/spec/listener_spec.rb +5 -5
- data/spec/worker_manager_spec.rb +3 -3
- metadata +56 -17
@@ -0,0 +1,387 @@
|
|
1
|
+
require "gorgon_bunny/compatibility"
|
2
|
+
|
3
|
+
module GorgonBunny
|
4
|
+
# Represents AMQP 0.9.1 queue.
|
5
|
+
#
|
6
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
7
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
8
|
+
class Queue
|
9
|
+
|
10
|
+
include GorgonBunny::Compatibility
|
11
|
+
|
12
|
+
|
13
|
+
#
|
14
|
+
# API
|
15
|
+
#
|
16
|
+
|
17
|
+
# @return [GorgonBunny::Channel] Channel this queue uses
|
18
|
+
attr_reader :channel
|
19
|
+
# @return [String] Queue name
|
20
|
+
attr_reader :name
|
21
|
+
# @return [Hash] Options this queue was created with
|
22
|
+
attr_reader :options
|
23
|
+
|
24
|
+
# @param [GorgonBunny::Channel] channel_or_connection Channel this queue will use. {GorgonBunny::Session} instances are supported only for
|
25
|
+
# backwards compatibility with 0.8.
|
26
|
+
# @param [String] name Queue name. Pass an empty string to make RabbitMQ generate a unique one.
|
27
|
+
# @param [Hash] opts Queue properties
|
28
|
+
#
|
29
|
+
# @option opts [Boolean] :durable (false) Should this queue be durable?
|
30
|
+
# @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects?
|
31
|
+
# @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
|
32
|
+
# @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
33
|
+
#
|
34
|
+
# @see GorgonBunny::Channel#queue
|
35
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
36
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
37
|
+
# @api public
|
38
|
+
def initialize(channel_or_connection, name = GorgonAMQ::Protocol::EMPTY_STRING, opts = {})
|
39
|
+
# old GorgonBunny versions pass a connection here. In that case,
|
40
|
+
# we just use default channel from it. MK.
|
41
|
+
@channel = channel_from(channel_or_connection)
|
42
|
+
@name = name
|
43
|
+
@options = self.class.add_default_options(name, opts)
|
44
|
+
@consumers = Hash.new
|
45
|
+
|
46
|
+
@durable = @options[:durable]
|
47
|
+
@exclusive = @options[:exclusive]
|
48
|
+
@server_named = @name.empty?
|
49
|
+
@auto_delete = @options[:auto_delete]
|
50
|
+
@arguments = @options[:arguments]
|
51
|
+
|
52
|
+
@bindings = Array.new
|
53
|
+
|
54
|
+
@default_consumer = nil
|
55
|
+
|
56
|
+
declare! unless opts[:no_declare]
|
57
|
+
|
58
|
+
@channel.register_queue(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Boolean] true if this queue was declared as durable (will survive broker restart).
|
62
|
+
# @api public
|
63
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
64
|
+
def durable?
|
65
|
+
@durable
|
66
|
+
end # durable?
|
67
|
+
|
68
|
+
# @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer)
|
69
|
+
# @api public
|
70
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
71
|
+
def exclusive?
|
72
|
+
@exclusive
|
73
|
+
end # exclusive?
|
74
|
+
|
75
|
+
# @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds).
|
76
|
+
# @api public
|
77
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
78
|
+
def auto_delete?
|
79
|
+
@auto_delete
|
80
|
+
end # auto_delete?
|
81
|
+
|
82
|
+
# @return [Boolean] true if this queue was declared as server named.
|
83
|
+
# @api public
|
84
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
85
|
+
def server_named?
|
86
|
+
@server_named
|
87
|
+
end # server_named?
|
88
|
+
|
89
|
+
# @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
90
|
+
# @api public
|
91
|
+
def arguments
|
92
|
+
@arguments
|
93
|
+
end
|
94
|
+
|
95
|
+
# Binds queue to an exchange
|
96
|
+
#
|
97
|
+
# @param [GorgonBunny::Exchange,String] exchange Exchange to bind to
|
98
|
+
# @param [Hash] opts Binding properties
|
99
|
+
#
|
100
|
+
# @option opts [String] :routing_key Routing key
|
101
|
+
# @option opts [Hash] :arguments ({}) Additional optional binding arguments
|
102
|
+
#
|
103
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
104
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
105
|
+
# @api public
|
106
|
+
def bind(exchange, opts = {})
|
107
|
+
@channel.queue_bind(@name, exchange, opts)
|
108
|
+
|
109
|
+
exchange_name = if exchange.respond_to?(:name)
|
110
|
+
exchange.name
|
111
|
+
else
|
112
|
+
exchange
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# store bindings for automatic recovery. We need to be very careful to
|
117
|
+
# not cause an infinite rebinding loop here when we recover. MK.
|
118
|
+
binding = { :exchange => exchange_name, :routing_key => (opts[:routing_key] || opts[:key]), :arguments => opts[:arguments] }
|
119
|
+
@bindings.push(binding) unless @bindings.include?(binding)
|
120
|
+
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
# Unbinds queue from an exchange
|
125
|
+
#
|
126
|
+
# @param [GorgonBunny::Exchange,String] exchange Exchange to unbind from
|
127
|
+
# @param [Hash] opts Binding properties
|
128
|
+
#
|
129
|
+
# @option opts [String] :routing_key Routing key
|
130
|
+
# @option opts [Hash] :arguments ({}) Additional optional binding arguments
|
131
|
+
#
|
132
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
133
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
134
|
+
# @api public
|
135
|
+
def unbind(exchange, opts = {})
|
136
|
+
@channel.queue_unbind(@name, exchange, opts)
|
137
|
+
|
138
|
+
exchange_name = if exchange.respond_to?(:name)
|
139
|
+
exchange.name
|
140
|
+
else
|
141
|
+
exchange
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
@bindings.delete_if { |b| b[:exchange] == exchange_name && b[:routing_key] == (opts[:routing_key] || opts[:key]) && b[:arguments] == opts[:arguments] }
|
146
|
+
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
# Adds a consumer to the queue (subscribes for message deliveries).
|
151
|
+
#
|
152
|
+
# @param [Hash] opts Options
|
153
|
+
#
|
154
|
+
# @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements?
|
155
|
+
# @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue?
|
156
|
+
# @option opts [Boolean] :block (false) Should the call block calling thread?
|
157
|
+
# @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin)
|
158
|
+
# @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let GorgonBunny generate it for you.
|
159
|
+
# @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions
|
160
|
+
#
|
161
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
162
|
+
# @api public
|
163
|
+
def subscribe(opts = {
|
164
|
+
:consumer_tag => @channel.generate_consumer_tag,
|
165
|
+
:ack => false,
|
166
|
+
:exclusive => false,
|
167
|
+
:block => false,
|
168
|
+
:on_cancellation => nil
|
169
|
+
}, &block)
|
170
|
+
|
171
|
+
ctag = opts.fetch(:consumer_tag, @channel.generate_consumer_tag)
|
172
|
+
consumer = Consumer.new(@channel,
|
173
|
+
self,
|
174
|
+
ctag,
|
175
|
+
!(opts[:ack] || opts[:manual_ack]),
|
176
|
+
opts[:exclusive],
|
177
|
+
opts[:arguments])
|
178
|
+
|
179
|
+
consumer.on_delivery(&block)
|
180
|
+
consumer.on_cancellation(&opts[:on_cancellation]) if opts[:on_cancellation]
|
181
|
+
|
182
|
+
@channel.basic_consume_with(consumer)
|
183
|
+
if opts[:block]
|
184
|
+
# joins current thread with the consumers pool, will block
|
185
|
+
# the current thread for as long as the consumer pool is active
|
186
|
+
@channel.work_pool.join
|
187
|
+
end
|
188
|
+
|
189
|
+
consumer
|
190
|
+
end
|
191
|
+
|
192
|
+
# Adds a consumer object to the queue (subscribes for message deliveries).
|
193
|
+
#
|
194
|
+
# @param [GorgonBunny::Consumer] consumer a {GorgonBunny::Consumer} subclass that implements consumer interface
|
195
|
+
# @param [Hash] opts Options
|
196
|
+
#
|
197
|
+
# @option opts [Boolean] block (false) Should the call block calling thread?
|
198
|
+
#
|
199
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
200
|
+
# @api public
|
201
|
+
def subscribe_with(consumer, opts = {:block => false})
|
202
|
+
@channel.basic_consume_with(consumer)
|
203
|
+
|
204
|
+
@channel.work_pool.join if opts[:block]
|
205
|
+
consumer
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param [Hash] opts Options
|
209
|
+
#
|
210
|
+
# @option opts [Boolean] :ack (false) Will the message be acknowledged manually?
|
211
|
+
#
|
212
|
+
# @return [Array] Triple of delivery info, message properties and message content.
|
213
|
+
# If the queue is empty, all three will be nils.
|
214
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
215
|
+
# @see GorgonBunny::Queue#subscribe
|
216
|
+
# @api public
|
217
|
+
#
|
218
|
+
# @example
|
219
|
+
# conn = GorgonBunny.new
|
220
|
+
# conn.start
|
221
|
+
#
|
222
|
+
# ch = conn.create_channel
|
223
|
+
# q = ch.queue("test1")
|
224
|
+
# x = ch.default_exchange
|
225
|
+
# x.publish("Hello, everybody!", :routing_key => 'test1')
|
226
|
+
#
|
227
|
+
# delivery_info, properties, payload = q.pop
|
228
|
+
#
|
229
|
+
# puts "This is the message: " + payload + "\n\n"
|
230
|
+
# conn.close
|
231
|
+
def pop(opts = {:ack => false}, &block)
|
232
|
+
delivery_info, properties, content = @channel.basic_get(@name, opts)
|
233
|
+
|
234
|
+
if block
|
235
|
+
block.call(delivery_info, properties, content)
|
236
|
+
else
|
237
|
+
[delivery_info, properties, content]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
alias get pop
|
241
|
+
|
242
|
+
# Version of {GorgonBunny::Queue#pop} that returns data in legacy format
|
243
|
+
# (as a hash).
|
244
|
+
# @return [Hash]
|
245
|
+
# @deprecated
|
246
|
+
def pop_as_hash(opts = {:ack => false}, &block)
|
247
|
+
delivery_info, properties, content = @channel.basic_get(@name, opts)
|
248
|
+
|
249
|
+
result = {:header => properties, :payload => content, :delivery_details => delivery_info}
|
250
|
+
|
251
|
+
if block
|
252
|
+
block.call(result)
|
253
|
+
else
|
254
|
+
result
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# Publishes a message to the queue via default exchange. Takes the same arguments
|
260
|
+
# as {GorgonBunny::Exchange#publish}
|
261
|
+
#
|
262
|
+
# @see GorgonBunny::Exchange#publish
|
263
|
+
# @see GorgonBunny::Channel#default_exchange
|
264
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
265
|
+
def publish(payload, opts = {})
|
266
|
+
@channel.default_exchange.publish(payload, opts.merge(:routing_key => @name))
|
267
|
+
|
268
|
+
self
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
# Deletes the queue
|
273
|
+
#
|
274
|
+
# @param [Hash] opts Options
|
275
|
+
#
|
276
|
+
# @option opts [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers?
|
277
|
+
# @option opts [Boolean] if_empty (false) Should this queue be deleted only if it has no messages?
|
278
|
+
#
|
279
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
280
|
+
# @api public
|
281
|
+
def delete(opts = {})
|
282
|
+
@channel.deregister_queue(self)
|
283
|
+
@channel.queue_delete(@name, opts)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Purges a queue (removes all messages from it)
|
287
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
288
|
+
# @api public
|
289
|
+
def purge(opts = {})
|
290
|
+
@channel.queue_purge(@name, opts)
|
291
|
+
|
292
|
+
self
|
293
|
+
end
|
294
|
+
|
295
|
+
# @return [Hash] A hash with information about the number of queue messages and consumers
|
296
|
+
# @see #message_count
|
297
|
+
# @see #consumer_count
|
298
|
+
def status
|
299
|
+
queue_declare_ok = @channel.queue_declare(@name, @options.merge(:passive => true))
|
300
|
+
{:message_count => queue_declare_ok.message_count,
|
301
|
+
:consumer_count => queue_declare_ok.consumer_count}
|
302
|
+
end
|
303
|
+
|
304
|
+
# @return [Integer] How many messages the queue has ready (e.g. not delivered but not unacknowledged)
|
305
|
+
def message_count
|
306
|
+
s = self.status
|
307
|
+
s[:message_count]
|
308
|
+
end
|
309
|
+
|
310
|
+
# @return [Integer] How many active consumers the queue has
|
311
|
+
def consumer_count
|
312
|
+
s = self.status
|
313
|
+
s[:consumer_count]
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# Recovery
|
318
|
+
#
|
319
|
+
|
320
|
+
# @private
|
321
|
+
def recover_from_network_failure
|
322
|
+
if self.server_named?
|
323
|
+
old_name = @name.dup
|
324
|
+
@name = GorgonAMQ::Protocol::EMPTY_STRING
|
325
|
+
|
326
|
+
@channel.deregister_queue_named(old_name)
|
327
|
+
end
|
328
|
+
|
329
|
+
# TODO: inject and use logger
|
330
|
+
# puts "Recovering queue #{@name}"
|
331
|
+
begin
|
332
|
+
declare!
|
333
|
+
|
334
|
+
@channel.register_queue(self)
|
335
|
+
rescue Exception => e
|
336
|
+
# TODO: inject and use logger
|
337
|
+
puts "Caught #{e.inspect} while redeclaring and registering #{@name}!"
|
338
|
+
end
|
339
|
+
recover_bindings
|
340
|
+
end
|
341
|
+
|
342
|
+
# @private
|
343
|
+
def recover_bindings
|
344
|
+
@bindings.each do |b|
|
345
|
+
# TODO: inject and use logger
|
346
|
+
# puts "Recovering binding #{b.inspect}"
|
347
|
+
self.bind(b[:exchange], b)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
#
|
353
|
+
# Implementation
|
354
|
+
#
|
355
|
+
|
356
|
+
# @private
|
357
|
+
def declare!
|
358
|
+
queue_declare_ok = @channel.queue_declare(@name, @options)
|
359
|
+
@name = queue_declare_ok.queue
|
360
|
+
end
|
361
|
+
|
362
|
+
protected
|
363
|
+
|
364
|
+
# @private
|
365
|
+
def self.add_default_options(name, opts, block)
|
366
|
+
{ :queue => name, :nowait => (block.nil? && !name.empty?) }.merge(opts)
|
367
|
+
end
|
368
|
+
|
369
|
+
# @private
|
370
|
+
def self.add_default_options(name, opts)
|
371
|
+
# :nowait is always false for GorgonBunny
|
372
|
+
h = { :queue => name, :nowait => false }.merge(opts)
|
373
|
+
|
374
|
+
if name.empty?
|
375
|
+
{
|
376
|
+
:passive => false,
|
377
|
+
:durable => false,
|
378
|
+
:exclusive => false,
|
379
|
+
:auto_delete => false,
|
380
|
+
:arguments => nil
|
381
|
+
}.merge(h)
|
382
|
+
else
|
383
|
+
h
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module GorgonBunny
|
4
|
+
# Network activity loop that reads and passes incoming AMQP 0.9.1 methods for
|
5
|
+
# processing. They are dispatched further down the line in GorgonBunny::Session and GorgonBunny::Channel.
|
6
|
+
# This loop uses a separate thread internally.
|
7
|
+
#
|
8
|
+
# This mimics the way RabbitMQ Java is designed quite closely.
|
9
|
+
# @private
|
10
|
+
class ReaderLoop
|
11
|
+
|
12
|
+
def initialize(transport, session, session_thread)
|
13
|
+
@transport = transport
|
14
|
+
@session = session
|
15
|
+
@session_thread = session_thread
|
16
|
+
@logger = @session.logger
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def start
|
21
|
+
@thread = Thread.new(&method(:run_loop))
|
22
|
+
end
|
23
|
+
|
24
|
+
def resume
|
25
|
+
start
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def run_loop
|
30
|
+
loop do
|
31
|
+
begin
|
32
|
+
break if @stopping || @network_is_down
|
33
|
+
run_once
|
34
|
+
rescue Errno::EBADF => ebadf
|
35
|
+
break if @stopping
|
36
|
+
# ignored, happens when we loop after the transport has already been closed
|
37
|
+
rescue GorgonAMQ::Protocol::EmptyResponseError, IOError, SystemCallError => e
|
38
|
+
break if @stopping
|
39
|
+
log_exception(e)
|
40
|
+
|
41
|
+
@network_is_down = true
|
42
|
+
|
43
|
+
if @session.automatically_recover?
|
44
|
+
@session.handle_network_failure(e)
|
45
|
+
else
|
46
|
+
@session_thread.raise(GorgonBunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
47
|
+
end
|
48
|
+
rescue ShutdownSignal => _
|
49
|
+
break
|
50
|
+
rescue Exception => e
|
51
|
+
break if @stopping
|
52
|
+
log_exception(e)
|
53
|
+
|
54
|
+
@network_is_down = true
|
55
|
+
@session_thread.raise(GorgonBunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@stopped = true
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_once
|
63
|
+
frame = @transport.read_next_frame
|
64
|
+
return if frame.is_a?(GorgonAMQ::Protocol::HeartbeatFrame)
|
65
|
+
|
66
|
+
if !frame.final? || frame.method_class.has_content?
|
67
|
+
header = @transport.read_next_frame
|
68
|
+
content = ''
|
69
|
+
|
70
|
+
if header.body_size > 0
|
71
|
+
loop do
|
72
|
+
body_frame = @transport.read_next_frame
|
73
|
+
content << body_frame.decode_payload
|
74
|
+
|
75
|
+
break if content.bytesize >= header.body_size
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@session.handle_frameset(frame.channel, [frame.decode_payload, header.decode_payload, content])
|
80
|
+
else
|
81
|
+
@session.handle_frame(frame.channel, frame.decode_payload)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def stop
|
86
|
+
@stopping = true
|
87
|
+
end
|
88
|
+
|
89
|
+
def stopped?
|
90
|
+
@stopped
|
91
|
+
end
|
92
|
+
|
93
|
+
def raise(e)
|
94
|
+
@thread.raise(e) if @thread
|
95
|
+
end
|
96
|
+
|
97
|
+
def join
|
98
|
+
@thread.join if @thread
|
99
|
+
end
|
100
|
+
|
101
|
+
def kill
|
102
|
+
if @thread
|
103
|
+
@thread.kill
|
104
|
+
@thread.join
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def log_exception(e)
|
109
|
+
@logger.error "Exception in the reader loop: #{e.class.name}: #{e.message}"
|
110
|
+
@logger.error "Backtrace: "
|
111
|
+
e.backtrace.each do |line|
|
112
|
+
@logger.error "\t#{line}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|