march_hare 2.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|