march_hare 2.0.0-java
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/lib/ext/commons-io.jar +0 -0
- data/lib/ext/rabbitmq-client.jar +0 -0
- data/lib/hot_bunnies.rb +2 -0
- data/lib/march_hare.rb +33 -0
- data/lib/march_hare/channel.rb +952 -0
- data/lib/march_hare/consumers.rb +2 -0
- data/lib/march_hare/consumers/base.rb +121 -0
- data/lib/march_hare/consumers/blocking.rb +73 -0
- data/lib/march_hare/exceptions.rb +174 -0
- data/lib/march_hare/exchange.rb +179 -0
- data/lib/march_hare/juc.rb +9 -0
- data/lib/march_hare/metadata.rb +93 -0
- data/lib/march_hare/queue.rb +265 -0
- data/lib/march_hare/session.rb +440 -0
- data/lib/march_hare/shutdown_listener.rb +15 -0
- data/lib/march_hare/thread_pools.rb +32 -0
- data/lib/march_hare/version.rb +5 -0
- data/lib/march_hare/versioned_delivery_tag.rb +28 -0
- metadata +63 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module MarchHare
|
2
|
+
class Headers
|
3
|
+
attr_reader :channel, :consumer_tag, :envelope, :properties
|
4
|
+
|
5
|
+
def initialize(channel, consumer_tag, envelope, properties)
|
6
|
+
@channel = channel
|
7
|
+
@consumer_tag = consumer_tag
|
8
|
+
@envelope = envelope
|
9
|
+
@properties = properties
|
10
|
+
end
|
11
|
+
|
12
|
+
def ack(options={})
|
13
|
+
@channel.basic_ack(delivery_tag, options.fetch(:multiple, false))
|
14
|
+
end
|
15
|
+
|
16
|
+
def reject(options={})
|
17
|
+
@channel.basic_reject(delivery_tag, options.fetch(:requeue, false))
|
18
|
+
end
|
19
|
+
|
20
|
+
begin :envelope_delegation
|
21
|
+
[
|
22
|
+
:routing_key,
|
23
|
+
:redeliver,
|
24
|
+
:exchange
|
25
|
+
].each do |envelope_property|
|
26
|
+
define_method(envelope_property) { @envelope.__send__(envelope_property) }
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :redelivered?, :redeliver
|
30
|
+
end
|
31
|
+
|
32
|
+
def delivery_tag
|
33
|
+
@delivery_tag ||= VersionedDeliveryTag.new(@envelope.delivery_tag, @channel.recoveries_counter.get)
|
34
|
+
end
|
35
|
+
|
36
|
+
begin :message_properties_delegation
|
37
|
+
[
|
38
|
+
:content_encoding,
|
39
|
+
:content_type,
|
40
|
+
:content_encoding,
|
41
|
+
:headers,
|
42
|
+
:delivery_mode,
|
43
|
+
:priority,
|
44
|
+
:correlation_id,
|
45
|
+
:reply_to,
|
46
|
+
:expiration,
|
47
|
+
:message_id,
|
48
|
+
:timestamp,
|
49
|
+
:type,
|
50
|
+
:user_id,
|
51
|
+
:app_id,
|
52
|
+
:cluster_id
|
53
|
+
].each do |properties_property|
|
54
|
+
define_method(properties_property) { @properties.__send__(properties_property) }
|
55
|
+
end # each
|
56
|
+
end
|
57
|
+
|
58
|
+
def persistent?
|
59
|
+
delivery_mode == 2
|
60
|
+
end
|
61
|
+
|
62
|
+
def redelivered?
|
63
|
+
redeliver
|
64
|
+
end
|
65
|
+
|
66
|
+
def redelivery?
|
67
|
+
redeliver
|
68
|
+
end
|
69
|
+
end # Headers
|
70
|
+
|
71
|
+
|
72
|
+
class BasicPropertiesBuilder
|
73
|
+
def self.build_properties_from(props = {})
|
74
|
+
builder = AMQP::BasicProperties::Builder.new
|
75
|
+
|
76
|
+
builder.content_type(props[:content_type]).
|
77
|
+
content_encoding(props[:content_encoding]).
|
78
|
+
headers(props[:headers]).
|
79
|
+
delivery_mode(props[:persistent] ? 2 : 1).
|
80
|
+
priority(props[:priority]).
|
81
|
+
correlation_id(props[:correlation_id]).
|
82
|
+
reply_to(props[:reply_to]).
|
83
|
+
expiration(if props[:expiration] then props[:expiration].to_s end).
|
84
|
+
message_id(props[:message_id]).
|
85
|
+
timestamp(props[:timestamp]).
|
86
|
+
type(props[:type]).
|
87
|
+
user_id(props[:user_id]).
|
88
|
+
app_id(props[:app_id]).
|
89
|
+
cluster_id(props[:cluster_id]).
|
90
|
+
build
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end # MarchHare
|
@@ -0,0 +1,265 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "march_hare/juc"
|
4
|
+
require "march_hare/metadata"
|
5
|
+
require "march_hare/consumers"
|
6
|
+
require "set"
|
7
|
+
|
8
|
+
module MarchHare
|
9
|
+
# Represents AMQP 0.9.1 queue.
|
10
|
+
#
|
11
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
12
|
+
# @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
|
13
|
+
class Queue
|
14
|
+
# @return [MarchHare::Channel] Channel this queue uses
|
15
|
+
attr_reader :channel
|
16
|
+
# @return [String] Queue name
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
# @param [MarchHare::Channel] channel_or_connection Channel this queue will use.
|
20
|
+
# @param [String] name Queue name. Pass an empty string to make RabbitMQ generate a unique one.
|
21
|
+
# @param [Hash] opts Queue properties
|
22
|
+
#
|
23
|
+
# @option opts [Boolean] :durable (false) Should this queue be durable?
|
24
|
+
# @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects?
|
25
|
+
# @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
|
26
|
+
# @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
27
|
+
#
|
28
|
+
# @see MarchHare::Channel#queue
|
29
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
30
|
+
# @see http://rubymarchhare.info/articles/extensions.html RabbitMQ Extensions guide
|
31
|
+
def initialize(channel, name, options={})
|
32
|
+
@channel = channel
|
33
|
+
@name = name
|
34
|
+
@options = {:durable => false, :exclusive => false, :auto_delete => false, :passive => false, :arguments => Hash.new}.merge(options)
|
35
|
+
|
36
|
+
@durable = @options[:durable]
|
37
|
+
@exclusive = @options[:exclusive]
|
38
|
+
@server_named = @name.empty?
|
39
|
+
@auto_delete = @options[:auto_delete]
|
40
|
+
@arguments = @options[:arguments]
|
41
|
+
|
42
|
+
@bindings = Set.new
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# @return [Boolean] true if this queue was declared as durable (will survive broker restart).
|
47
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
48
|
+
def durable?
|
49
|
+
@durable
|
50
|
+
end # durable?
|
51
|
+
|
52
|
+
# @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer)
|
53
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
54
|
+
def exclusive?
|
55
|
+
@exclusive
|
56
|
+
end # exclusive?
|
57
|
+
|
58
|
+
# @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds).
|
59
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
60
|
+
def auto_delete?
|
61
|
+
@auto_delete
|
62
|
+
end # auto_delete?
|
63
|
+
|
64
|
+
# @return [Boolean] true if this queue was declared as server named.
|
65
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
66
|
+
def server_named?
|
67
|
+
@server_named
|
68
|
+
end # server_named?
|
69
|
+
|
70
|
+
# @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
71
|
+
def arguments
|
72
|
+
@arguments
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
# Binds queue to an exchange
|
78
|
+
#
|
79
|
+
# @param [MarchHare::Exchange,String] exchange Exchange to bind to
|
80
|
+
# @param [Hash] options Binding properties
|
81
|
+
#
|
82
|
+
# @option options [String] :routing_key Routing key
|
83
|
+
# @option options [Hash] :arguments ({}) Additional optional binding arguments
|
84
|
+
#
|
85
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
86
|
+
# @see http://rubymarchhare.info/articles/bindings.html Bindings guide
|
87
|
+
def bind(exchange, options={})
|
88
|
+
exchange_name = if exchange.respond_to?(:name) then
|
89
|
+
exchange.name
|
90
|
+
else
|
91
|
+
exchange.to_s
|
92
|
+
end
|
93
|
+
@channel.queue_bind(@name, exchange_name, (options[:routing_key] || options[:key] || ""), options[:arguments])
|
94
|
+
|
95
|
+
# store bindings for automatic recovery. We need to be very careful to
|
96
|
+
# not cause an infinite rebinding loop here when we recover. MK.
|
97
|
+
binding = { :exchange => exchange_name, :routing_key => (options[:routing_key] || options[:key]), :arguments => options[:arguments] }
|
98
|
+
@bindings << binding unless @bindings.include?(binding)
|
99
|
+
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
# Unbinds queue from an exchange
|
104
|
+
#
|
105
|
+
# @param [MarchHare::Exchange,String] exchange Exchange to unbind from
|
106
|
+
# @param [Hash] options Binding properties
|
107
|
+
#
|
108
|
+
# @option options [String] :routing_key Routing key
|
109
|
+
# @option options [Hash] :arguments ({}) Additional optional binding arguments
|
110
|
+
#
|
111
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
112
|
+
# @see http://rubymarchhare.info/articles/bindings.html Bindings guide
|
113
|
+
def unbind(exchange, options={})
|
114
|
+
exchange_name = if exchange.respond_to?(:name) then
|
115
|
+
exchange.name
|
116
|
+
else
|
117
|
+
exchange.to_s
|
118
|
+
end
|
119
|
+
@channel.queue_unbind(@name, exchange_name, options.fetch(:routing_key, ''))
|
120
|
+
|
121
|
+
binding = { :exchange => exchange_name, :routing_key => (options[:routing_key] || options[:key] || ""), :arguments => options[:arguments] }
|
122
|
+
@bindings.delete(binding) unless @bindings.include?(binding)
|
123
|
+
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
# Deletes the queue
|
128
|
+
#
|
129
|
+
# @option [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers?
|
130
|
+
# @option [Boolean] if_empty (false) Should this queue be deleted only if it has no messages?
|
131
|
+
#
|
132
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
133
|
+
def delete(if_unused = false, if_empty = false)
|
134
|
+
@channel.queue_delete(@name, if_unused, if_empty)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Purges a queue (removes all messages from it)
|
138
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
139
|
+
# @api public
|
140
|
+
def purge
|
141
|
+
@channel.queue_purge(@name)
|
142
|
+
end
|
143
|
+
|
144
|
+
def get(options = {:block => false})
|
145
|
+
response = @channel.basic_get(@name, !options.fetch(:ack, false))
|
146
|
+
|
147
|
+
if response
|
148
|
+
[Headers.new(@channel, nil, response.envelope, response.props), String.from_java_bytes(response.body)]
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
alias pop get
|
154
|
+
|
155
|
+
def build_consumer(opts = {}, &block)
|
156
|
+
if opts[:block] || opts[:blocking]
|
157
|
+
BlockingCallbackConsumer.new(@channel, self, opts[:buffer_size], opts, block)
|
158
|
+
else
|
159
|
+
CallbackConsumer.new(@channel, self, opts, block)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Adds a consumer to the queue (subscribes for message deliveries).
|
164
|
+
#
|
165
|
+
# @param [Hash] opts Options
|
166
|
+
#
|
167
|
+
# @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements?
|
168
|
+
# @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue?
|
169
|
+
# @option opts [Boolean] :block (false) Should the call block calling thread?
|
170
|
+
# @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin)
|
171
|
+
# @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let MarchHare generate it for you.
|
172
|
+
# @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions
|
173
|
+
#
|
174
|
+
# @see http://rubymarchhare.info/articles/queues.html Queues and Consumers guide
|
175
|
+
# @api public
|
176
|
+
def subscribe(opts = {}, &block)
|
177
|
+
subscribe_with(build_consumer(opts, &block), opts)
|
178
|
+
end
|
179
|
+
|
180
|
+
def subscribe_with(consumer, opts = {})
|
181
|
+
@consumer_tag = @channel.basic_consume(@name, !(opts[:ack] || opts[:manual_ack]), consumer)
|
182
|
+
consumer.consumer_tag = @consumer_tag
|
183
|
+
|
184
|
+
@default_consumer = consumer
|
185
|
+
@channel.register_consumer(@consumer_tag, consumer)
|
186
|
+
|
187
|
+
consumer.start
|
188
|
+
consumer
|
189
|
+
end
|
190
|
+
|
191
|
+
# @return [Array<Integer>] A pair with information about the number of queue messages and consumers
|
192
|
+
# @see #message_count
|
193
|
+
# @see #consumer_count
|
194
|
+
def status
|
195
|
+
response = @channel.queue_declare_passive(@name)
|
196
|
+
[response.message_count, response.consumer_count]
|
197
|
+
end
|
198
|
+
|
199
|
+
# @return [Integer] How many messages the queue has ready (e.g. not delivered but not unacknowledged)
|
200
|
+
def message_count
|
201
|
+
response = @channel.queue_declare_passive(@name)
|
202
|
+
response.message_count
|
203
|
+
end
|
204
|
+
|
205
|
+
# @return [Integer] How many active consumers the queue has
|
206
|
+
def consumer_count
|
207
|
+
response = @channel.queue_declare_passive(@name)
|
208
|
+
response.consumer_count
|
209
|
+
end
|
210
|
+
|
211
|
+
# Publishes a message to the queue via default exchange. Takes the same arguments
|
212
|
+
# as {MarchHare::Exchange#publish}
|
213
|
+
#
|
214
|
+
# @see MarchHare::Exchange#publish
|
215
|
+
# @see MarchHare::Channel#default_exchange
|
216
|
+
def publish(payload, opts = {})
|
217
|
+
@channel.default_exchange.publish(payload, opts.merge(:routing_key => @name))
|
218
|
+
|
219
|
+
self
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
#
|
224
|
+
# Implementation
|
225
|
+
#
|
226
|
+
|
227
|
+
# @return [Boolean] true if this queue is a pre-defined one (amq.direct, amq.fanout, amq.match and so on)
|
228
|
+
def predefined?
|
229
|
+
@name.start_with?("amq.")
|
230
|
+
end
|
231
|
+
|
232
|
+
# @private
|
233
|
+
def declare!
|
234
|
+
response = if @options[:passive]
|
235
|
+
then @channel.queue_declare_passive(@name)
|
236
|
+
else @channel.queue_declare(@name, @options[:durable], @options[:exclusive], @options[:auto_delete], @options[:arguments])
|
237
|
+
end
|
238
|
+
@name = response.queue
|
239
|
+
end
|
240
|
+
|
241
|
+
# @private
|
242
|
+
def recover_from_network_failure
|
243
|
+
if self.server_named?
|
244
|
+
old_name = @name.dup
|
245
|
+
@name = ""
|
246
|
+
|
247
|
+
@channel.deregister_queue_named(old_name)
|
248
|
+
end
|
249
|
+
|
250
|
+
declare! if !predefined?
|
251
|
+
|
252
|
+
@channel.register_queue(self)
|
253
|
+
recover_bindings
|
254
|
+
end
|
255
|
+
|
256
|
+
# @private
|
257
|
+
def recover_bindings
|
258
|
+
@bindings.each do |b|
|
259
|
+
# TODO: use a logger
|
260
|
+
# puts "Recovering binding #{b.inspect}"
|
261
|
+
self.bind(b[:exchange], b)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end # Queue
|
265
|
+
end # MarchHare
|
@@ -0,0 +1,440 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "march_hare/shutdown_listener"
|
3
|
+
require "set"
|
4
|
+
require "march_hare/thread_pools"
|
5
|
+
|
6
|
+
module MarchHare
|
7
|
+
java_import com.rabbitmq.client.ConnectionFactory
|
8
|
+
java_import com.rabbitmq.client.Connection
|
9
|
+
java_import com.rabbitmq.client.BlockedListener
|
10
|
+
|
11
|
+
# Connection to a RabbitMQ node.
|
12
|
+
#
|
13
|
+
# Used to open and close connections and open (create) new channels.
|
14
|
+
#
|
15
|
+
# @see .connect
|
16
|
+
# @see #create_channel
|
17
|
+
# @see #close
|
18
|
+
# @see http://rubymarchhare.info/articles/getting_started.html Getting Started guide
|
19
|
+
# @see http://rubymarchhare.info/articles/connecting.html Connecting to RabbitMQ guide
|
20
|
+
class Session
|
21
|
+
|
22
|
+
#
|
23
|
+
# API
|
24
|
+
#
|
25
|
+
|
26
|
+
# Default reconnection interval for TCP connection failures
|
27
|
+
DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
|
28
|
+
|
29
|
+
# Connects to a RabbitMQ node.
|
30
|
+
#
|
31
|
+
# @param [Hash] options Connection options
|
32
|
+
#
|
33
|
+
# @option options [String] :host ("127.0.0.1") Hostname or IP address to connect to
|
34
|
+
# @option options [Integer] :port (5672) Port RabbitMQ listens on
|
35
|
+
# @option options [String] :username ("guest") Username
|
36
|
+
# @option options [String] :password ("guest") Password
|
37
|
+
# @option options [String] :vhost ("/") Virtual host to use
|
38
|
+
# @option options [Integer] :heartbeat (600) Heartbeat interval. 0 means no heartbeat.
|
39
|
+
# @option options [Boolean] :tls (false) Set to true to use TLS/SSL connection. This will switch port to 5671 by default.
|
40
|
+
#
|
41
|
+
# @see http://rubymarchhare.info/articles/connecting.html Connecting to RabbitMQ guide
|
42
|
+
def self.connect(options={})
|
43
|
+
cf = ConnectionFactory.new
|
44
|
+
|
45
|
+
cf.uri = options[:uri] if options[:uri]
|
46
|
+
cf.host = hostname_from(options) if include_host?(options)
|
47
|
+
cf.port = options[:port].to_i if options[:port]
|
48
|
+
cf.virtual_host = vhost_from(options) if include_vhost?(options)
|
49
|
+
cf.connection_timeout = timeout_from(options) if include_timeout?(options)
|
50
|
+
cf.username = username_from(options) if include_username?(options)
|
51
|
+
cf.password = password_from(options) if include_password?(options)
|
52
|
+
|
53
|
+
cf.requested_heartbeat = heartbeat_from(options) if include_heartbeat?(options)
|
54
|
+
cf.connection_timeout = connection_timeout_from(options) if include_connection_timeout?(options)
|
55
|
+
|
56
|
+
tls = (options[:ssl] || options[:tls])
|
57
|
+
case tls
|
58
|
+
when true then
|
59
|
+
cf.use_ssl_protocol
|
60
|
+
when String then
|
61
|
+
if options[:trust_manager]
|
62
|
+
cf.use_ssl_protocol(tls, options[:trust_manager])
|
63
|
+
else
|
64
|
+
cf.use_ssl_protocol(tls)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
new(cf, options)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Array<MarchHare::Channel>] Channels opened on this connection
|
73
|
+
attr_reader :channels
|
74
|
+
|
75
|
+
|
76
|
+
# @private
|
77
|
+
def initialize(connection_factory, opts = {})
|
78
|
+
@cf = connection_factory
|
79
|
+
# executors cannot be restarted after shutdown,
|
80
|
+
# so we really need a factory here. MK.
|
81
|
+
@executor_factory = opts[:executor_factory] || build_executor_factory_from(opts)
|
82
|
+
@connection = self.new_connection
|
83
|
+
@channels = JavaConcurrent::ConcurrentHashMap.new
|
84
|
+
|
85
|
+
# should automatic recovery from network failures be used?
|
86
|
+
@automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil?
|
87
|
+
true
|
88
|
+
else
|
89
|
+
opts[:automatically_recover] || opts[:automatic_recovery]
|
90
|
+
end
|
91
|
+
@network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
|
92
|
+
@shutdown_hooks = Array.new
|
93
|
+
|
94
|
+
if @automatically_recover
|
95
|
+
self.add_automatic_recovery_hook
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Opens a new channel.
|
100
|
+
#
|
101
|
+
# @param [Integer] (nil): Channel number. Pass nil to let MarchHare allocate an available number
|
102
|
+
# in a safe way.
|
103
|
+
#
|
104
|
+
# @return [MarchHare::Channel] Newly created channel
|
105
|
+
# @see MarchHare::Channel
|
106
|
+
# @see http://rubymarchhare.info/articles/getting_started.html Getting Started guide
|
107
|
+
def create_channel(n = nil)
|
108
|
+
jc = if n
|
109
|
+
@connection.create_channel(n)
|
110
|
+
else
|
111
|
+
@connection.create_channel
|
112
|
+
end
|
113
|
+
|
114
|
+
ch = Channel.new(self, jc)
|
115
|
+
register_channel(ch)
|
116
|
+
|
117
|
+
ch
|
118
|
+
end
|
119
|
+
|
120
|
+
# Closes connection gracefully.
|
121
|
+
#
|
122
|
+
# This includes shutting down consumer work pool gracefully,
|
123
|
+
# waiting up to 5 seconds for all consumer deliveries to be
|
124
|
+
# processed.
|
125
|
+
def close
|
126
|
+
@channels.select { |_, ch| ch.open? }.each do |_, ch|
|
127
|
+
ch.close
|
128
|
+
end
|
129
|
+
|
130
|
+
@connection.close
|
131
|
+
end
|
132
|
+
|
133
|
+
# @return [Boolean] true if connection is open, false otherwise
|
134
|
+
def open?
|
135
|
+
@connection.open?
|
136
|
+
end
|
137
|
+
alias connected? open?
|
138
|
+
|
139
|
+
# @return [Boolean] true if this channel is closed
|
140
|
+
def closed?
|
141
|
+
!@connection.open?
|
142
|
+
end
|
143
|
+
|
144
|
+
# Defines a shutdown event callback. Shutdown events are
|
145
|
+
# broadcasted when a connection is closed, either explicitly
|
146
|
+
# or forcefully, or due to a network/peer failure.
|
147
|
+
def on_shutdown(&block)
|
148
|
+
sh = ShutdownListener.new(self, &block)
|
149
|
+
@shutdown_hooks << sh
|
150
|
+
|
151
|
+
@connection.add_shutdown_listener(sh)
|
152
|
+
|
153
|
+
sh
|
154
|
+
end
|
155
|
+
|
156
|
+
# Defines a connection.blocked handler
|
157
|
+
def on_blocked(&block)
|
158
|
+
self.add_blocked_listener(BlockBlockedUnblockedListener.for_blocked(block))
|
159
|
+
end
|
160
|
+
|
161
|
+
# Defines a connection.unblocked handler
|
162
|
+
def on_unblocked(&block)
|
163
|
+
self.add_blocked_listener(BlockBlockedUnblockedListener.for_unblocked(block))
|
164
|
+
end
|
165
|
+
|
166
|
+
# Clears all callbacks defined with #on_blocked and #on_unblocked.
|
167
|
+
def clear_blocked_connection_callbacks
|
168
|
+
@connection.clear_blocked_listeners
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# @private
|
173
|
+
def add_automatic_recovery_hook
|
174
|
+
fn = Proc.new do |_, signal|
|
175
|
+
if !signal.initiated_by_application
|
176
|
+
self.automatically_recover
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
@automatic_recovery_hook = self.on_shutdown(&fn)
|
181
|
+
end
|
182
|
+
|
183
|
+
# @private
|
184
|
+
def disable_automatic_recovery
|
185
|
+
@connetion.remove_shutdown_listener(@automatic_recovery_hook) if @automatic_recovery_hook
|
186
|
+
end
|
187
|
+
|
188
|
+
# Begins automatic connection recovery (typically only used internally
|
189
|
+
# to recover from network failures)
|
190
|
+
def automatically_recover
|
191
|
+
ms = @network_recovery_interval * 1000
|
192
|
+
# recovering immediately makes little sense. Wait a bit first. MK.
|
193
|
+
java.lang.Thread.sleep(ms)
|
194
|
+
|
195
|
+
@connection = converting_rjc_exceptions_to_ruby do
|
196
|
+
reconnecting_on_network_failures(ms) do
|
197
|
+
self.new_connection
|
198
|
+
end
|
199
|
+
end
|
200
|
+
@thread_pool = ThreadPools.dynamically_growing
|
201
|
+
self.recover_shutdown_hooks
|
202
|
+
|
203
|
+
# sorting channels by id means that the cases like the following:
|
204
|
+
#
|
205
|
+
# ch1 = conn.create_channel
|
206
|
+
# ch2 = conn.create_channel
|
207
|
+
#
|
208
|
+
# x = ch1.topic("logs", :durable => false)
|
209
|
+
# q = ch2.queue("", :exclusive => true)
|
210
|
+
#
|
211
|
+
# q.bind(x)
|
212
|
+
#
|
213
|
+
# will recover correctly because exchanges and queues will be recovered
|
214
|
+
# in the order the user expects and before bindings.
|
215
|
+
@channels.sort_by {|id, _| id}.each do |id, ch|
|
216
|
+
begin
|
217
|
+
ch.automatically_recover(self, @connection)
|
218
|
+
rescue Exception, java.io.IOException => e
|
219
|
+
# TODO: logging
|
220
|
+
$stderr.puts e
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# @private
|
226
|
+
def recover_shutdown_hooks
|
227
|
+
@shutdown_hooks.each do |sh|
|
228
|
+
@connection.add_shutdown_listener(sh)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Flushes the socket used by this connection.
|
233
|
+
def flush
|
234
|
+
@connection.flush
|
235
|
+
end
|
236
|
+
|
237
|
+
# @private
|
238
|
+
def heartbeat=(n)
|
239
|
+
@connection.heartbeat = n
|
240
|
+
end
|
241
|
+
|
242
|
+
# No-op, exists for better API compatibility with Bunny.
|
243
|
+
def start
|
244
|
+
# no-op
|
245
|
+
#
|
246
|
+
# This method mimics Bunny::Session#start in Bunny 0.9.
|
247
|
+
# Without it, #method_missing will proxy the call to com.rabbitmq.client.AMQConnection,
|
248
|
+
# which happens to have a #start method which is not idempotent.
|
249
|
+
#
|
250
|
+
# So we stub out #start in case someone migrating from Bunny forgets to remove
|
251
|
+
# the call to #start. MK.
|
252
|
+
end
|
253
|
+
|
254
|
+
def method_missing(selector, *args)
|
255
|
+
@connection.__send__(selector, *args)
|
256
|
+
end
|
257
|
+
|
258
|
+
# @return [String]
|
259
|
+
def to_s
|
260
|
+
"#<#{self.class.name}:#{object_id} #{@cf.username}@#{@cf.host}:#{@cf.port}, vhost=#{@cf.virtual_host}>"
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
#
|
265
|
+
# Implementation
|
266
|
+
#
|
267
|
+
|
268
|
+
# @private
|
269
|
+
def register_channel(ch)
|
270
|
+
@channels[ch.channel_number] = ch
|
271
|
+
end
|
272
|
+
|
273
|
+
# @private
|
274
|
+
def unregister_channel(ch)
|
275
|
+
@channels.delete(ch.channel_number)
|
276
|
+
end
|
277
|
+
|
278
|
+
protected
|
279
|
+
|
280
|
+
# @private
|
281
|
+
def self.hostname_from(options)
|
282
|
+
options[:host] || options[:hostname] || ConnectionFactory::DEFAULT_HOST
|
283
|
+
end
|
284
|
+
|
285
|
+
# @private
|
286
|
+
def self.include_host?(options)
|
287
|
+
!!(options[:host] || options[:hostname])
|
288
|
+
end
|
289
|
+
|
290
|
+
# @private
|
291
|
+
def self.vhost_from(options)
|
292
|
+
options[:virtual_host] || options[:vhost] || ConnectionFactory::DEFAULT_VHOST
|
293
|
+
end
|
294
|
+
|
295
|
+
# @private
|
296
|
+
def self.include_vhost?(options)
|
297
|
+
!!(options[:virtual_host] || options[:vhost])
|
298
|
+
end
|
299
|
+
|
300
|
+
# @private
|
301
|
+
def self.timeout_from(options)
|
302
|
+
options[:connection_timeout] || options[:timeout]
|
303
|
+
end
|
304
|
+
|
305
|
+
# @private
|
306
|
+
def self.include_timeout?(options)
|
307
|
+
!!(options[:connection_timeout] || options[:timeout])
|
308
|
+
end
|
309
|
+
|
310
|
+
# @private
|
311
|
+
def self.username_from(options)
|
312
|
+
options[:username] || options[:user] || ConnectionFactory::DEFAULT_USER
|
313
|
+
end
|
314
|
+
|
315
|
+
# @private
|
316
|
+
def self.heartbeat_from(options)
|
317
|
+
options[:heartbeat_interval] || options[:requested_heartbeat] || ConnectionFactory::DEFAULT_HEARTBEAT
|
318
|
+
end
|
319
|
+
|
320
|
+
# @private
|
321
|
+
def self.connection_timeout_from(options)
|
322
|
+
options[:connection_timeout_interval] || options[:connection_timeout] || ConnectionFactory::DEFAULT_CONNECTION_TIMEOUT
|
323
|
+
end
|
324
|
+
|
325
|
+
# @private
|
326
|
+
def self.include_username?(options)
|
327
|
+
!!(options[:username] || options[:user])
|
328
|
+
end
|
329
|
+
|
330
|
+
# @private
|
331
|
+
def self.password_from(options)
|
332
|
+
options[:password] || options[:pass] || ConnectionFactory::DEFAULT_PASS
|
333
|
+
end
|
334
|
+
|
335
|
+
# @private
|
336
|
+
def self.include_password?(options)
|
337
|
+
!!(options[:password] || options[:pass])
|
338
|
+
end
|
339
|
+
|
340
|
+
# @private
|
341
|
+
def self.include_heartbeat?(options)
|
342
|
+
!!(options[:heartbeat_interval] || options[:requested_heartbeat] || options[:heartbeat])
|
343
|
+
end
|
344
|
+
|
345
|
+
# @private
|
346
|
+
def self.include_connection_timeout?(options)
|
347
|
+
!!(options[:connection_timeout_interval] || options[:connection_timeout])
|
348
|
+
end
|
349
|
+
|
350
|
+
# Executes a block, catching Java exceptions RabbitMQ Java client throws and
|
351
|
+
# transforms them to Ruby exceptions that are then re-raised.
|
352
|
+
#
|
353
|
+
# @private
|
354
|
+
def converting_rjc_exceptions_to_ruby(&block)
|
355
|
+
begin
|
356
|
+
block.call
|
357
|
+
rescue java.net.ConnectException => e
|
358
|
+
raise ConnectionRefused.new("Connection to #{@cf.host}:#{@cf.port} refused")
|
359
|
+
rescue java.net.UnknownHostException => e
|
360
|
+
raise ConnectionRefused.new("Connection to #{@cf.host}:#{@cf.port} refused: host unknown")
|
361
|
+
rescue com.rabbitmq.client.AuthenticationFailureException => e
|
362
|
+
raise AuthenticationFailureError.new(@cf.username, @cf.virtual_host, @cf.password.bytesize)
|
363
|
+
rescue com.rabbitmq.client.PossibleAuthenticationFailureException => e
|
364
|
+
raise PossibleAuthenticationFailureError.new(@cf.username, @cf.virtual_host, @cf.password.bytesize)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# @private
|
369
|
+
def reconnecting_on_network_failures(interval_in_ms, &fn)
|
370
|
+
begin
|
371
|
+
fn.call
|
372
|
+
rescue IOError, MarchHare::ConnectionRefused, java.io.IOException => e
|
373
|
+
java.lang.Thread.sleep(interval_in_ms)
|
374
|
+
|
375
|
+
retry
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# @private
|
380
|
+
def new_connection
|
381
|
+
converting_rjc_exceptions_to_ruby do
|
382
|
+
if @executor_factory
|
383
|
+
@cf.new_connection(@executor_factory.call)
|
384
|
+
else
|
385
|
+
@cf.new_connection
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# Makes it easier to construct executor factories.
|
391
|
+
# @private
|
392
|
+
def build_executor_factory_from(opts)
|
393
|
+
# if we are given a thread pool size, construct
|
394
|
+
# a callable that creates a fixed size executor
|
395
|
+
# of that size. MK.
|
396
|
+
if n = opts[:thread_pool_size]
|
397
|
+
return Proc.new { MarchHare::ThreadPools.fixed_of_size(n) }
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
# Ruby blocks-based BlockedListener that handles
|
402
|
+
# connection.blocked and connection.unblocked.
|
403
|
+
# @private
|
404
|
+
class BlockBlockedUnblockedListener
|
405
|
+
include com.rabbitmq.client.BlockedListener
|
406
|
+
|
407
|
+
def self.for_blocked(block)
|
408
|
+
new(block, noop_fn1)
|
409
|
+
end
|
410
|
+
|
411
|
+
def self.for_unblocked(block)
|
412
|
+
new(noop_fn0, block)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Returns a no-op function of arity 0.
|
416
|
+
def self.noop_fn0
|
417
|
+
Proc.new {}
|
418
|
+
end
|
419
|
+
|
420
|
+
# Returns a no-op function of arity 1.
|
421
|
+
def self.noop_fn1
|
422
|
+
Proc.new { |_| }
|
423
|
+
end
|
424
|
+
|
425
|
+
def initialize(on_blocked, on_unblocked)
|
426
|
+
@blocked = on_blocked
|
427
|
+
@unblocked = on_unblocked
|
428
|
+
end
|
429
|
+
|
430
|
+
def handle_blocked(reason)
|
431
|
+
@blocked.call(reason)
|
432
|
+
end
|
433
|
+
|
434
|
+
def handle_unblocked()
|
435
|
+
@unblocked.call()
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
end
|
440
|
+
end
|