garaio_bunny 2.19.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.
- 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
|