garaio_bunny 2.19.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +231 -0
- data/lib/amq/protocol/extensions.rb +16 -0
- data/lib/bunny/authentication/credentials_encoder.rb +55 -0
- data/lib/bunny/authentication/external_mechanism_encoder.rb +27 -0
- data/lib/bunny/authentication/plain_mechanism_encoder.rb +19 -0
- data/lib/bunny/channel.rb +2055 -0
- data/lib/bunny/channel_id_allocator.rb +82 -0
- data/lib/bunny/concurrent/atomic_fixnum.rb +75 -0
- data/lib/bunny/concurrent/condition.rb +66 -0
- data/lib/bunny/concurrent/continuation_queue.rb +62 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +61 -0
- data/lib/bunny/concurrent/synchronized_sorted_set.rb +56 -0
- data/lib/bunny/consumer.rb +128 -0
- data/lib/bunny/consumer_tag_generator.rb +23 -0
- data/lib/bunny/consumer_work_pool.rb +122 -0
- data/lib/bunny/cruby/socket.rb +110 -0
- data/lib/bunny/cruby/ssl_socket.rb +118 -0
- data/lib/bunny/delivery_info.rb +93 -0
- data/lib/bunny/exceptions.rb +269 -0
- data/lib/bunny/exchange.rb +275 -0
- data/lib/bunny/framing.rb +56 -0
- data/lib/bunny/get_response.rb +83 -0
- data/lib/bunny/heartbeat_sender.rb +71 -0
- data/lib/bunny/jruby/socket.rb +57 -0
- data/lib/bunny/jruby/ssl_socket.rb +58 -0
- data/lib/bunny/message_properties.rb +119 -0
- data/lib/bunny/queue.rb +393 -0
- data/lib/bunny/reader_loop.rb +158 -0
- data/lib/bunny/return_info.rb +74 -0
- data/lib/bunny/session.rb +1483 -0
- data/lib/bunny/socket.rb +14 -0
- data/lib/bunny/ssl_socket.rb +14 -0
- data/lib/bunny/test_kit.rb +41 -0
- data/lib/bunny/timeout.rb +7 -0
- data/lib/bunny/transport.rb +526 -0
- data/lib/bunny/version.rb +6 -0
- data/lib/bunny/versioned_delivery_tag.rb +28 -0
- data/lib/bunny.rb +92 -0
- metadata +127 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
module Bunny
|
2
|
+
# Wraps basic properties hash as returned by amq-protocol to
|
3
|
+
# provide access to the delivery properties as immutable hash as
|
4
|
+
# well as methods.
|
5
|
+
class MessageProperties
|
6
|
+
|
7
|
+
#
|
8
|
+
# Behaviors
|
9
|
+
#
|
10
|
+
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
#
|
14
|
+
# API
|
15
|
+
#
|
16
|
+
|
17
|
+
# @private
|
18
|
+
def initialize(properties)
|
19
|
+
@properties = properties
|
20
|
+
end
|
21
|
+
|
22
|
+
# Iterates over the message properties
|
23
|
+
# @see Enumerable#each
|
24
|
+
def each(*args, &block)
|
25
|
+
@properties.each(*args, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Accesses message properties by key
|
29
|
+
# @see Hash#[]
|
30
|
+
def [](k)
|
31
|
+
@properties[k]
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Hash] Hash representation of this delivery info
|
35
|
+
def to_hash
|
36
|
+
@properties
|
37
|
+
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def to_s
|
41
|
+
to_hash.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# @private
|
45
|
+
def inspect
|
46
|
+
to_hash.inspect
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String] (Optional) content type of the message, as set by the publisher
|
50
|
+
def content_type
|
51
|
+
@properties[:content_type]
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [String] (Optional) content encoding of the message, as set by the publisher
|
55
|
+
def content_encoding
|
56
|
+
@properties[:content_encoding]
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String] Message headers
|
60
|
+
def headers
|
61
|
+
@properties[:headers]
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Integer] Delivery mode (persistent or transient)
|
65
|
+
def delivery_mode
|
66
|
+
@properties[:delivery_mode]
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Integer] Message priority, as set by the publisher
|
70
|
+
def priority
|
71
|
+
@properties[:priority]
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [String] What message this message is a reply to (or corresponds to), as set by the publisher
|
75
|
+
def correlation_id
|
76
|
+
@properties[:correlation_id]
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [String] (Optional) How to reply to the publisher (usually a reply queue name)
|
80
|
+
def reply_to
|
81
|
+
@properties[:reply_to]
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [String] Message expiration, as set by the publisher
|
85
|
+
def expiration
|
86
|
+
@properties[:expiration]
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [String] Message ID, as set by the publisher
|
90
|
+
def message_id
|
91
|
+
@properties[:message_id]
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Time] Message timestamp, as set by the publisher
|
95
|
+
def timestamp
|
96
|
+
@properties[:timestamp]
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [String] Message type, as set by the publisher
|
100
|
+
def type
|
101
|
+
@properties[:type]
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [String] Publishing user, as set by the publisher
|
105
|
+
def user_id
|
106
|
+
@properties[:user_id]
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [String] Publishing application, as set by the publisher
|
110
|
+
def app_id
|
111
|
+
@properties[:app_id]
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [String] Cluster ID, as set by the publisher
|
115
|
+
def cluster_id
|
116
|
+
@properties[:cluster_id]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/bunny/queue.rb
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
require "bunny/get_response"
|
2
|
+
|
3
|
+
module Bunny
|
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
|
+
#
|
11
|
+
# API
|
12
|
+
#
|
13
|
+
|
14
|
+
# @return [Bunny::Channel] Channel this queue uses
|
15
|
+
attr_reader :channel
|
16
|
+
# @return [String] Queue name
|
17
|
+
attr_reader :name
|
18
|
+
# @return [Hash] Options this queue was created with
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
# @param [Bunny::Channel] channel Channel this queue will use.
|
22
|
+
# @param [String] name Queue name. Pass an empty string to make RabbitMQ generate a unique one.
|
23
|
+
# @param [Hash] opts Queue properties
|
24
|
+
#
|
25
|
+
# @option opts [Boolean] :durable (false) Should this queue be durable?
|
26
|
+
# @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects?
|
27
|
+
# @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
|
28
|
+
# @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
29
|
+
#
|
30
|
+
# @see Bunny::Channel#queue
|
31
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
32
|
+
# @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
|
33
|
+
# @api public
|
34
|
+
def initialize(channel, name = AMQ::Protocol::EMPTY_STRING, opts = {})
|
35
|
+
# old Bunny versions pass a connection here. In that case,
|
36
|
+
# we just use default channel from it. MK.
|
37
|
+
@channel = channel
|
38
|
+
@name = name
|
39
|
+
@options = self.class.add_default_options(name, opts)
|
40
|
+
|
41
|
+
@durable = @options[:durable]
|
42
|
+
@exclusive = @options[:exclusive]
|
43
|
+
@server_named = @name.empty?
|
44
|
+
@auto_delete = @options[:auto_delete]
|
45
|
+
@arguments = @options[:arguments]
|
46
|
+
|
47
|
+
@bindings = Array.new
|
48
|
+
|
49
|
+
@default_consumer = nil
|
50
|
+
|
51
|
+
declare! unless opts[:no_declare]
|
52
|
+
|
53
|
+
@channel.register_queue(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Boolean] true if this queue was declared as durable (will survive broker restart).
|
57
|
+
# @api public
|
58
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
59
|
+
def durable?
|
60
|
+
@durable
|
61
|
+
end # durable?
|
62
|
+
|
63
|
+
# @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer)
|
64
|
+
# @api public
|
65
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
66
|
+
def exclusive?
|
67
|
+
@exclusive
|
68
|
+
end # exclusive?
|
69
|
+
|
70
|
+
# @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds).
|
71
|
+
# @api public
|
72
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
73
|
+
def auto_delete?
|
74
|
+
@auto_delete
|
75
|
+
end # auto_delete?
|
76
|
+
|
77
|
+
# @return [Boolean] true if this queue was declared as server named.
|
78
|
+
# @api public
|
79
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
80
|
+
def server_named?
|
81
|
+
@server_named
|
82
|
+
end # server_named?
|
83
|
+
|
84
|
+
# @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
85
|
+
# @api public
|
86
|
+
def arguments
|
87
|
+
@arguments
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
oid = ("0x%x" % (self.object_id << 1))
|
92
|
+
"<#{self.class.name}:#{oid} @name=\"#{name}\" channel=#{@channel.to_s} @durable=#{@durable} @auto_delete=#{@auto_delete} @exclusive=#{@exclusive} @arguments=#{@arguments}>"
|
93
|
+
end
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
# Binds queue to an exchange
|
100
|
+
#
|
101
|
+
# @param [Bunny::Exchange,String] exchange Exchange to bind to
|
102
|
+
# @param [Hash] opts Binding properties
|
103
|
+
#
|
104
|
+
# @option opts [String] :routing_key Routing key
|
105
|
+
# @option opts [Hash] :arguments ({}) Additional optional binding arguments
|
106
|
+
#
|
107
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
108
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
109
|
+
# @api public
|
110
|
+
def bind(exchange, opts = {})
|
111
|
+
@channel.queue_bind(@name, exchange, opts)
|
112
|
+
|
113
|
+
exchange_name = if exchange.respond_to?(:name)
|
114
|
+
exchange.name
|
115
|
+
else
|
116
|
+
exchange
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# store bindings for automatic recovery. We need to be very careful to
|
121
|
+
# not cause an infinite rebinding loop here when we recover. MK.
|
122
|
+
binding = { :exchange => exchange_name, :routing_key => (opts[:routing_key] || opts[:key]), :arguments => opts[:arguments] }
|
123
|
+
@bindings.push(binding) unless @bindings.include?(binding)
|
124
|
+
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Unbinds queue from an exchange
|
129
|
+
#
|
130
|
+
# @param [Bunny::Exchange,String] exchange Exchange to unbind from
|
131
|
+
# @param [Hash] opts Binding properties
|
132
|
+
#
|
133
|
+
# @option opts [String] :routing_key Routing key
|
134
|
+
# @option opts [Hash] :arguments ({}) Additional optional binding arguments
|
135
|
+
#
|
136
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
137
|
+
# @see http://rubybunny.info/articles/bindings.html Bindings guide
|
138
|
+
# @api public
|
139
|
+
def unbind(exchange, opts = {})
|
140
|
+
@channel.queue_unbind(@name, exchange, opts)
|
141
|
+
|
142
|
+
exchange_name = if exchange.respond_to?(:name)
|
143
|
+
exchange.name
|
144
|
+
else
|
145
|
+
exchange
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
@bindings.delete_if { |b| b[:exchange] == exchange_name && b[:routing_key] == (opts[:routing_key] || opts[:key]) && b[:arguments] == opts[:arguments] }
|
150
|
+
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
# Adds a consumer to the queue (subscribes for message deliveries).
|
155
|
+
#
|
156
|
+
# @param [Hash] opts Options
|
157
|
+
#
|
158
|
+
# @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead
|
159
|
+
# @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements?
|
160
|
+
# @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue?
|
161
|
+
# @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin)
|
162
|
+
# @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let Bunny generate it for you.
|
163
|
+
# @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions
|
164
|
+
#
|
165
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
166
|
+
# @api public
|
167
|
+
def subscribe(opts = {
|
168
|
+
:consumer_tag => @channel.generate_consumer_tag,
|
169
|
+
:manual_ack => false,
|
170
|
+
:exclusive => false,
|
171
|
+
:block => false,
|
172
|
+
:on_cancellation => nil
|
173
|
+
}, &block)
|
174
|
+
|
175
|
+
unless opts[:ack].nil?
|
176
|
+
warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
|
177
|
+
opts[:manual_ack] = opts[:ack]
|
178
|
+
end
|
179
|
+
|
180
|
+
ctag = opts.fetch(:consumer_tag, @channel.generate_consumer_tag)
|
181
|
+
consumer = Consumer.new(@channel,
|
182
|
+
self,
|
183
|
+
ctag,
|
184
|
+
!opts[:manual_ack],
|
185
|
+
opts[:exclusive],
|
186
|
+
opts[:arguments])
|
187
|
+
|
188
|
+
consumer.on_delivery(&block)
|
189
|
+
consumer.on_cancellation(&opts[:on_cancellation]) if opts[:on_cancellation]
|
190
|
+
|
191
|
+
@channel.basic_consume_with(consumer)
|
192
|
+
if opts[:block]
|
193
|
+
# joins current thread with the consumers pool, will block
|
194
|
+
# the current thread for as long as the consumer pool is active
|
195
|
+
@channel.work_pool.join
|
196
|
+
end
|
197
|
+
|
198
|
+
consumer
|
199
|
+
end
|
200
|
+
|
201
|
+
# Adds a consumer object to the queue (subscribes for message deliveries).
|
202
|
+
#
|
203
|
+
# @param [Bunny::Consumer] consumer a {Bunny::Consumer} subclass that implements consumer interface
|
204
|
+
# @param [Hash] opts Options
|
205
|
+
#
|
206
|
+
# @option opts [Boolean] block (false) Should the call block calling thread?
|
207
|
+
#
|
208
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
209
|
+
# @api public
|
210
|
+
def subscribe_with(consumer, opts = {:block => false})
|
211
|
+
@channel.basic_consume_with(consumer)
|
212
|
+
|
213
|
+
@channel.work_pool.join if opts[:block]
|
214
|
+
consumer
|
215
|
+
end
|
216
|
+
|
217
|
+
# @param [Hash] opts Options
|
218
|
+
#
|
219
|
+
# @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead
|
220
|
+
# @option opts [Boolean] :manual_ack (false) Will the message be acknowledged manually?
|
221
|
+
#
|
222
|
+
# @return [Array] Triple of delivery info, message properties and message content.
|
223
|
+
# If the queue is empty, all three will be nils.
|
224
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
225
|
+
# @see Bunny::Queue#subscribe
|
226
|
+
# @api public
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
# conn = Bunny.new
|
230
|
+
# conn.start
|
231
|
+
#
|
232
|
+
# ch = conn.create_channel
|
233
|
+
# q = ch.queue("test1")
|
234
|
+
# x = ch.default_exchange
|
235
|
+
# x.publish("Hello, everybody!", :routing_key => 'test1')
|
236
|
+
#
|
237
|
+
# delivery_info, properties, payload = q.pop
|
238
|
+
#
|
239
|
+
# puts "This is the message: " + payload + "\n\n"
|
240
|
+
# conn.close
|
241
|
+
def pop(opts = {:manual_ack => false}, &block)
|
242
|
+
unless opts[:ack].nil?
|
243
|
+
warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
|
244
|
+
opts[:manual_ack] = opts[:ack]
|
245
|
+
end
|
246
|
+
|
247
|
+
get_response, properties, content = @channel.basic_get(@name, opts)
|
248
|
+
|
249
|
+
if block
|
250
|
+
if properties
|
251
|
+
di = GetResponse.new(get_response, @channel)
|
252
|
+
mp = MessageProperties.new(properties)
|
253
|
+
|
254
|
+
block.call(di, mp, content)
|
255
|
+
else
|
256
|
+
block.call(nil, nil, nil)
|
257
|
+
end
|
258
|
+
else
|
259
|
+
if properties
|
260
|
+
di = GetResponse.new(get_response, @channel)
|
261
|
+
mp = MessageProperties.new(properties)
|
262
|
+
[di, mp, content]
|
263
|
+
else
|
264
|
+
[nil, nil, nil]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
alias get pop
|
269
|
+
|
270
|
+
# Publishes a message to the queue via default exchange. Takes the same arguments
|
271
|
+
# as {Bunny::Exchange#publish}
|
272
|
+
#
|
273
|
+
# @see Bunny::Exchange#publish
|
274
|
+
# @see Bunny::Channel#default_exchange
|
275
|
+
# @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
|
276
|
+
def publish(payload, opts = {})
|
277
|
+
@channel.default_exchange.publish(payload, opts.merge(:routing_key => @name))
|
278
|
+
|
279
|
+
self
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
# Deletes the queue
|
284
|
+
#
|
285
|
+
# @param [Hash] opts Options
|
286
|
+
#
|
287
|
+
# @option opts [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers?
|
288
|
+
# @option opts [Boolean] if_empty (false) Should this queue be deleted only if it has no messages?
|
289
|
+
#
|
290
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
291
|
+
# @api public
|
292
|
+
def delete(opts = {})
|
293
|
+
@channel.deregister_queue(self)
|
294
|
+
@channel.queue_delete(@name, opts)
|
295
|
+
end
|
296
|
+
|
297
|
+
# Purges a queue (removes all messages from it)
|
298
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
299
|
+
# @api public
|
300
|
+
def purge(opts = {})
|
301
|
+
@channel.queue_purge(@name, opts)
|
302
|
+
|
303
|
+
self
|
304
|
+
end
|
305
|
+
|
306
|
+
# @return [Hash] A hash with information about the number of queue messages and consumers
|
307
|
+
# @see #message_count
|
308
|
+
# @see #consumer_count
|
309
|
+
def status
|
310
|
+
queue_declare_ok = @channel.queue_declare(@name, @options.merge(:passive => true))
|
311
|
+
{:message_count => queue_declare_ok.message_count,
|
312
|
+
:consumer_count => queue_declare_ok.consumer_count}
|
313
|
+
end
|
314
|
+
|
315
|
+
# @return [Integer] How many messages the queue has ready (e.g. not delivered but not unacknowledged)
|
316
|
+
def message_count
|
317
|
+
s = self.status
|
318
|
+
s[:message_count]
|
319
|
+
end
|
320
|
+
|
321
|
+
# @return [Integer] How many active consumers the queue has
|
322
|
+
def consumer_count
|
323
|
+
s = self.status
|
324
|
+
s[:consumer_count]
|
325
|
+
end
|
326
|
+
|
327
|
+
#
|
328
|
+
# Recovery
|
329
|
+
#
|
330
|
+
|
331
|
+
# @private
|
332
|
+
def recover_from_network_failure
|
333
|
+
if self.server_named?
|
334
|
+
old_name = @name.dup
|
335
|
+
@name = AMQ::Protocol::EMPTY_STRING
|
336
|
+
|
337
|
+
@channel.deregister_queue_named(old_name)
|
338
|
+
end
|
339
|
+
|
340
|
+
# TODO: inject and use logger
|
341
|
+
# puts "Recovering queue #{@name}"
|
342
|
+
begin
|
343
|
+
declare! unless @options[:no_declare]
|
344
|
+
|
345
|
+
@channel.register_queue(self)
|
346
|
+
rescue Exception => e
|
347
|
+
# TODO: inject and use logger
|
348
|
+
puts "Caught #{e.inspect} while redeclaring and registering #{@name}!"
|
349
|
+
end
|
350
|
+
recover_bindings
|
351
|
+
end
|
352
|
+
|
353
|
+
# @private
|
354
|
+
def recover_bindings
|
355
|
+
@bindings.each do |b|
|
356
|
+
# TODO: inject and use logger
|
357
|
+
# puts "Recovering binding #{b.inspect}"
|
358
|
+
self.bind(b[:exchange], b)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
#
|
364
|
+
# Implementation
|
365
|
+
#
|
366
|
+
|
367
|
+
# @private
|
368
|
+
def declare!
|
369
|
+
queue_declare_ok = @channel.queue_declare(@name, @options)
|
370
|
+
@name = queue_declare_ok.queue
|
371
|
+
end
|
372
|
+
|
373
|
+
protected
|
374
|
+
|
375
|
+
# @private
|
376
|
+
def self.add_default_options(name, opts)
|
377
|
+
# :nowait is always false for Bunny
|
378
|
+
h = { :queue => name, :nowait => false }.merge(opts)
|
379
|
+
|
380
|
+
if name.empty?
|
381
|
+
{
|
382
|
+
:passive => false,
|
383
|
+
:durable => false,
|
384
|
+
:exclusive => false,
|
385
|
+
:auto_delete => false,
|
386
|
+
:arguments => nil
|
387
|
+
}.merge(h)
|
388
|
+
else
|
389
|
+
h
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Bunny
|
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 Bunny::Session and Bunny::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_error_handler)
|
13
|
+
@transport = transport
|
14
|
+
@session = session
|
15
|
+
@session_error_handler = session_error_handler
|
16
|
+
@logger = @session.logger
|
17
|
+
|
18
|
+
@mutex = Mutex.new
|
19
|
+
|
20
|
+
@stopping = false
|
21
|
+
@stopped = false
|
22
|
+
@network_is_down = false
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def start
|
27
|
+
@thread = Thread.new(&method(:run_loop))
|
28
|
+
end
|
29
|
+
|
30
|
+
def resume
|
31
|
+
start
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def run_loop
|
36
|
+
loop do
|
37
|
+
begin
|
38
|
+
break if @mutex.synchronize { @stopping || @stopped || @network_is_down }
|
39
|
+
run_once
|
40
|
+
rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Timeout::Error,
|
41
|
+
OpenSSL::OpenSSLError => e
|
42
|
+
break if terminate? || @session.closing? || @session.closed?
|
43
|
+
|
44
|
+
@network_is_down = true
|
45
|
+
if @session.automatically_recover?
|
46
|
+
log_exception(e, level: :warn)
|
47
|
+
@session.handle_network_failure(e)
|
48
|
+
else
|
49
|
+
log_exception(e)
|
50
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
51
|
+
end
|
52
|
+
rescue ShutdownSignal => _
|
53
|
+
@mutex.synchronize { @stopping = true }
|
54
|
+
break
|
55
|
+
rescue Exception => e
|
56
|
+
break if terminate?
|
57
|
+
if !(@session.closing? || @session.closed?)
|
58
|
+
log_exception(e)
|
59
|
+
|
60
|
+
@network_is_down = true
|
61
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
|
62
|
+
end
|
63
|
+
rescue Errno::EBADF => _ebadf
|
64
|
+
break if terminate?
|
65
|
+
# ignored, happens when we loop after the transport has already been closed
|
66
|
+
@mutex.synchronize { @stopping = true }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@mutex.synchronize { @stopped = true }
|
71
|
+
end
|
72
|
+
|
73
|
+
def run_once
|
74
|
+
frame = @transport.read_next_frame
|
75
|
+
return if frame.is_a?(AMQ::Protocol::HeartbeatFrame)
|
76
|
+
|
77
|
+
if !frame.final? || frame.method_class.has_content?
|
78
|
+
header = @transport.read_next_frame
|
79
|
+
content = ''
|
80
|
+
|
81
|
+
if header.body_size > 0
|
82
|
+
loop do
|
83
|
+
body_frame = @transport.read_next_frame
|
84
|
+
content << body_frame.decode_payload
|
85
|
+
|
86
|
+
break if content.bytesize >= header.body_size
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
@session.handle_frameset(frame.channel, [frame.decode_payload, header.decode_payload, content])
|
91
|
+
else
|
92
|
+
@session.handle_frame(frame.channel, frame.decode_payload)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def stop
|
97
|
+
@mutex.synchronize { @stopping = true }
|
98
|
+
end
|
99
|
+
|
100
|
+
def stopped?
|
101
|
+
@mutex.synchronize { @stopped }
|
102
|
+
end
|
103
|
+
|
104
|
+
def stopping?
|
105
|
+
@mutex.synchronize { @stopping }
|
106
|
+
end
|
107
|
+
|
108
|
+
def terminate_with(e)
|
109
|
+
@mutex.synchronize { @stopping = true }
|
110
|
+
|
111
|
+
self.raise(e)
|
112
|
+
end
|
113
|
+
|
114
|
+
def raise(e)
|
115
|
+
@thread.raise(e) if @thread
|
116
|
+
end
|
117
|
+
|
118
|
+
def join
|
119
|
+
# Thread#join can/would trigger a re-raise of an unhandled exception in this thread.
|
120
|
+
# In addition, Thread.handle_interrupt can be used by other libraries or application code
|
121
|
+
# that would make this join operation fail with an obscure exception.
|
122
|
+
# So we try to save everyone some really unpleasant debugging time by introducing
|
123
|
+
# this condition which typically would not evaluate to true anyway.
|
124
|
+
#
|
125
|
+
# See ruby-amqp/bunny#589 and ruby-amqp/bunny#590 for background.
|
126
|
+
@thread.join if @thread && @thread != Thread.current
|
127
|
+
end
|
128
|
+
|
129
|
+
def kill
|
130
|
+
if @thread
|
131
|
+
@thread.kill
|
132
|
+
@thread.join
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
protected
|
137
|
+
|
138
|
+
def log_exception(e, level: :error)
|
139
|
+
if !(io_error?(e) && (@session.closing? || @session.closed?))
|
140
|
+
@logger.send level, "Exception in the reader loop: #{e.class.name}: #{e.message}"
|
141
|
+
@logger.send level, "Backtrace: "
|
142
|
+
e.backtrace.each do |line|
|
143
|
+
@logger.send level, "\t#{line}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def io_error?(e)
|
149
|
+
[AMQ::Protocol::EmptyResponseError, IOError, SystemCallError].any? do |klazz|
|
150
|
+
e.is_a?(klazz)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def terminate?
|
155
|
+
@mutex.synchronize { @stopping || @stopped }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|