hot_bunnies 2.0.0.pre9-java → 2.0.0.pre10-java

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require "hot_bunnies/shutdown_listener"
3
+ require "hot_bunnies/juc"
3
4
 
4
5
  module HotBunnies
5
6
  # ## Channels in RabbitMQ
@@ -121,10 +122,14 @@ module HotBunnies
121
122
  @connection = session
122
123
  @delegate = delegate
123
124
 
124
- # we keep track of consumers to gracefully shut down their
125
+ @exchanges = ConcurrentHashMap.new
126
+ @queues = ConcurrentHashMap.new
127
+ # we keep track of consumers in part to gracefully shut down their
125
128
  # executors when the channel is closed. This frees library users
126
129
  # from having to worry about this. MK.
127
- @consumers = ConcurrentHashMap.new
130
+ @consumers = ConcurrentHashMap.new
131
+ @shutdown_hooks = ConcurrentSkipListSet.new
132
+ @recoveries_counter = JavaConcurrent::AtomicInteger.new(0)
128
133
 
129
134
  on_shutdown do |ch, cause|
130
135
  ch.gracefully_shut_down_consumers
@@ -180,11 +185,84 @@ module HotBunnies
180
185
 
181
186
  def on_shutdown(&block)
182
187
  sh = ShutdownListener.new(self, &block)
183
- @connection.add_shutdown_listener(sh)
188
+
189
+ @shutdown_hooks << sh
190
+ @delegate.add_shutdown_listener(sh)
184
191
 
185
192
  sh
186
193
  end
187
194
 
195
+ # @private
196
+ def automatically_recover(session, java_connection)
197
+ jch = java_connection.create_channel(id)
198
+
199
+ self.revive_with(jch)
200
+ self.recover_shutdown_hooks
201
+
202
+ self.recover_prefetch_setting
203
+ self.recover_exchanges
204
+ # this includes recovering bindings
205
+ self.recover_queues
206
+ self.recover_consumers
207
+ self.increment_recoveries_counter
208
+ end
209
+
210
+ # @private
211
+ def revive_with(java_ch)
212
+ @delegate = java_ch
213
+ end
214
+
215
+ # @private
216
+ def recover_shutdown_hooks
217
+ @shutdown_hooks.each do |sh|
218
+ @delegate.add_shutdown_listener(sh)
219
+ end
220
+ end
221
+
222
+ # Recovers basic.qos setting. Used by the Automatic Network Failure
223
+ # Recovery feature.
224
+ #
225
+ # @api plugin
226
+ def recover_prefetch_setting
227
+ basic_qos(@prefetch_count) if @prefetch_count
228
+ end
229
+
230
+ # Recovers exchanges. Used by the Automatic Network Failure
231
+ # Recovery feature.
232
+ #
233
+ # @api plugin
234
+ def recover_exchanges
235
+ @exchanges.values.each do |x|
236
+ x.recover_from_network_failure
237
+ end
238
+ end
239
+
240
+ # Recovers queues and bindings. Used by the Automatic Network Failure
241
+ # Recovery feature.
242
+ #
243
+ # @api private
244
+ def recover_queues
245
+ @queues.values.each do |q|
246
+ q.recover_from_network_failure
247
+ end
248
+ end
249
+
250
+ # Recovers consumers. Used by the Automatic Network Failure
251
+ # Recovery feature.
252
+ #
253
+ # @api private
254
+ def recover_consumers
255
+ @consumers.values.each do |c|
256
+ self.unregister_consumer(c)
257
+ c.recover_from_network_failure
258
+ end
259
+ end
260
+
261
+ # @private
262
+ def increment_recoveries_counter
263
+ @recoveries_counter.increment
264
+ end
265
+
188
266
  # @group Exchanges
189
267
 
190
268
  # Declares a headers exchange or looks it up in the cache of previously
@@ -202,9 +280,11 @@ module HotBunnies
202
280
  # @see http://hotbunnies.info/articles/exchanges.html Exchanges and Publishing guide
203
281
  # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
204
282
  def exchange(name, options={})
205
- Exchange.new(self, name, options).tap do |x|
283
+ dx = Exchange.new(self, name, options).tap do |x|
206
284
  x.declare!
207
285
  end
286
+
287
+ self.register_exchange(dx)
208
288
  end
209
289
 
210
290
  # Declares a fanout exchange or looks it up in the cache of previously
@@ -222,9 +302,11 @@ module HotBunnies
222
302
  # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
223
303
  # @api public
224
304
  def fanout(name, opts = {})
225
- Exchange.new(self, name, opts.merge(:type => "fanout")).tap do |x|
305
+ dx = Exchange.new(self, name, opts.merge(:type => "fanout")).tap do |x|
226
306
  x.declare!
227
307
  end
308
+
309
+ self.register_exchange(dx)
228
310
  end
229
311
 
230
312
  # Declares a direct exchange or looks it up in the cache of previously
@@ -242,9 +324,11 @@ module HotBunnies
242
324
  # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
243
325
  # @api public
244
326
  def direct(name, opts = {})
245
- Exchange.new(self, name, opts.merge(:type => "direct")).tap do |x|
327
+ dx = Exchange.new(self, name, opts.merge(:type => "direct")).tap do |x|
246
328
  x.declare!
247
329
  end
330
+
331
+ self.register_exchange(dx)
248
332
  end
249
333
 
250
334
  # Declares a topic exchange or looks it up in the cache of previously
@@ -262,9 +346,11 @@ module HotBunnies
262
346
  # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
263
347
  # @api public
264
348
  def topic(name, opts = {})
265
- Exchange.new(self, name, opts.merge(:type => "topic")).tap do |x|
349
+ dx = Exchange.new(self, name, opts.merge(:type => "topic")).tap do |x|
266
350
  x.declare!
267
351
  end
352
+
353
+ self.register_exchange(dx)
268
354
  end
269
355
 
270
356
  # Declares a headers exchange or looks it up in the cache of previously
@@ -282,9 +368,11 @@ module HotBunnies
282
368
  # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
283
369
  # @api public
284
370
  def headers(name, opts = {})
285
- Exchange.new(self, name, opts.merge(:type => "headers")).tap do |x|
371
+ dx = Exchange.new(self, name, opts.merge(:type => "headers")).tap do |x|
286
372
  x.declare!
287
373
  end
374
+
375
+ self.register_exchange(dx)
288
376
  end
289
377
 
290
378
  # Provides access to the default exchange
@@ -330,9 +418,11 @@ module HotBunnies
330
418
  # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions guide
331
419
  # @api public
332
420
  def queue(name, options={})
333
- Queue.new(self, name, options).tap do |q|
421
+ dq = Queue.new(self, name, options).tap do |q|
334
422
  q.declare!
335
423
  end
424
+
425
+ self.register_queue(dq)
336
426
  end
337
427
 
338
428
  # Declares a queue using queue.declare AMQP 0.9.1 method.
@@ -477,15 +567,22 @@ module HotBunnies
477
567
  end
478
568
 
479
569
  def basic_consume(queue, auto_ack, consumer)
480
- converting_rjc_exceptions_to_ruby do
570
+ consumer.auto_ack = auto_ack
571
+ tag = converting_rjc_exceptions_to_ruby do
481
572
  @delegate.basic_consume(queue, auto_ack, consumer)
482
573
  end
574
+ self.register_consumer(tag, consumer)
575
+
576
+ tag
483
577
  end
484
578
 
485
579
  def basic_qos(prefetch_count)
486
- converting_rjc_exceptions_to_ruby do
580
+ r = converting_rjc_exceptions_to_ruby do
487
581
  @delegate.basic_qos(prefetch_count)
488
582
  end
583
+ @prefetch_count = prefetch_count
584
+
585
+ r
489
586
  end
490
587
 
491
588
  def qos(options={})
@@ -726,6 +823,36 @@ module HotBunnies
726
823
  end
727
824
  end
728
825
 
826
+ # @private
827
+ def deregister_queue(queue)
828
+ @queues.delete(queue.name)
829
+ end
830
+
831
+ # @private
832
+ def deregister_queue_named(name)
833
+ @queues.delete(name)
834
+ end
835
+
836
+ # @private
837
+ def register_queue(queue)
838
+ @queues[queue.name] = queue
839
+ end
840
+
841
+ # @private
842
+ def find_queue(name)
843
+ @queues[name]
844
+ end
845
+
846
+ # @private
847
+ def deregister_exchange(exchange)
848
+ @exchanges.delete(exchange.name)
849
+ end
850
+
851
+ # @private
852
+ def register_exchange(exchange)
853
+ @exchanges[exchange.name] = exchange
854
+ end
855
+
729
856
  # @private
730
857
  def register_consumer(consumer_tag, consumer)
731
858
  @consumers[consumer_tag] = consumer
@@ -3,10 +3,13 @@ module HotBunnies
3
3
 
4
4
  class BaseConsumer < DefaultConsumer
5
5
  attr_accessor :consumer_tag
6
+ attr_accessor :auto_ack
6
7
 
7
- def initialize(channel)
8
+ def initialize(channel, queue)
8
9
  super(channel)
9
10
  @channel = channel
11
+ @queue = queue
12
+ @auto_ack = true
10
13
 
11
14
  @cancelling = JavaConcurrent::AtomicBoolean.new
12
15
  @cancelled = JavaConcurrent::AtomicBoolean.new
@@ -14,8 +17,8 @@ module HotBunnies
14
17
  @terminated = JavaConcurrent::AtomicBoolean.new
15
18
  end
16
19
 
17
- def handleDelivery(consumer_tag, envelope, properties, body)
18
- body = String.from_java_bytes(body)
20
+ def handleDelivery(consumer_tag, envelope, properties, bytes)
21
+ body = String.from_java_bytes(bytes)
19
22
  headers = Headers.new(channel, consumer_tag, envelope, properties)
20
23
 
21
24
  deliver(headers, body)
@@ -68,5 +71,15 @@ module HotBunnies
68
71
  def terminated?
69
72
  @terminated.get
70
73
  end
74
+
75
+ # @private
76
+ def recover_from_network_failure
77
+ @consumer_tag = @channel.basic_consume(@queue.name, @auto_ack, self)
78
+
79
+ @terminated.set(false)
80
+ @cancelled.set(false)
81
+
82
+ @consumer_tag
83
+ end
71
84
  end
72
85
  end
@@ -6,8 +6,8 @@ module HotBunnies
6
6
 
7
7
  POISON = :__poison__
8
8
 
9
- def initialize(channel, buffer_size, opts, callback)
10
- super(channel, callback)
9
+ def initialize(channel, queue, buffer_size, opts, callback)
10
+ super(channel, queue, callback)
11
11
  if buffer_size
12
12
  @internal_queue = ArrayBlockingQueue.new(buffer_size)
13
13
  else
@@ -2,10 +2,10 @@ require "hot_bunnies/consumers/base"
2
2
 
3
3
  module HotBunnies
4
4
  class CallbackConsumer < BaseConsumer
5
- def initialize(channel, callback)
5
+ def initialize(channel, queue, callback)
6
6
  raise ArgumentError, "callback must not be nil!" if callback.nil?
7
7
 
8
- super(channel)
8
+ super(channel, queue)
9
9
  @callback = callback
10
10
  @callback_arity = @callback.arity
11
11
  end
@@ -20,11 +20,20 @@ module HotBunnies
20
20
  end
21
21
 
22
22
  class AsyncCallbackConsumer < CallbackConsumer
23
- def initialize(channel, opts, callback, executor)
24
- super(channel, callback)
25
- @executor = executor
26
- @executor_submit = executor.java_method(:submit, [JavaConcurrent::Runnable.java_class])
27
- @opts = opts
23
+ def initialize(channel, queue, opts, callback)
24
+ super(channel, queue, callback)
25
+
26
+ # during connection recovery, the executor may be shut down, e.g. due to
27
+ # an exception. So we need a way to create a duplicate. Unfortunately, since
28
+ # the executor can be passed as an argument, we cannot know how it was
29
+ # instantiated. Instead we require a lambda to produce instance of an executor
30
+ # as a workaround. MK.
31
+ @executor_factory = opts.fetch(:executor_factory, Proc.new {
32
+ JavaConcurrent::Executors.new_single_thread_executor
33
+ })
34
+ @executor = opts.fetch(:executor, @executor_factory.call)
35
+ @executor_submit = @executor.java_method(:submit, [JavaConcurrent::Runnable.java_class])
36
+ @opts = opts
28
37
  end
29
38
 
30
39
  def deliver(headers, message)
@@ -32,8 +41,9 @@ module HotBunnies
32
41
  @executor_submit.call do
33
42
  begin
34
43
  callback(headers, message)
35
- rescue Exception => e
36
- $stderr.puts "Unhandled exception in consumer #{@consumer_tag}: #{e.message}"
44
+ rescue Exception, java.lang.Throwable => e
45
+ # TODO: logging
46
+ $stderr.puts "Unhandled exception in consumer #{@consumer_tag}: #{e}"
37
47
  end
38
48
  end
39
49
  end
@@ -69,5 +79,15 @@ module HotBunnies
69
79
  end
70
80
  alias maybe_shut_down_executor gracefully_shut_down
71
81
  alias gracefully_shutdown gracefully_shut_down
82
+
83
+
84
+ # @private
85
+ def recover_from_network_failure
86
+ # ensure we have a functioning executor. MK.
87
+ @executor = @executor_factory.call
88
+ @executor_submit = @executor.java_method(:submit, [JavaConcurrent::Runnable.java_class])
89
+
90
+ super
91
+ end
72
92
  end
73
93
  end
@@ -52,5 +52,11 @@ module HotBunnies
52
52
  end
53
53
  end
54
54
  end
55
+
56
+ # @private
57
+ def recover_from_network_failure
58
+ # puts "Recovering exchange #{@name} from network failure"
59
+ declare! unless predefined?
60
+ end
55
61
  end
56
62
  end
@@ -7,4 +7,5 @@ module JavaConcurrent
7
7
  java_import 'java.util.concurrent.ArrayBlockingQueue'
8
8
  java_import 'java.util.concurrent.TimeUnit'
9
9
  java_import 'java.util.concurrent.atomic.AtomicBoolean'
10
+ java_import 'java.util.concurrent.atomic.AtomicInteger'
10
11
  end
@@ -3,33 +3,144 @@
3
3
  require "hot_bunnies/juc"
4
4
  require "hot_bunnies/metadata"
5
5
  require "hot_bunnies/consumers"
6
+ require "set"
6
7
 
7
8
  module HotBunnies
9
+ # Represents AMQP 0.9.1 queue.
10
+ #
11
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
12
+ # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions guide
8
13
  class Queue
9
- attr_reader :name, :channel
14
+ # @return [HotBunnies::Channel] Channel this queue uses
15
+ attr_reader :channel
16
+ # @return [String] Queue name
17
+ attr_reader :name
10
18
 
19
+ # @param [HotBunnies::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 HotBunnies::Channel#queue
29
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
30
+ # @see http://hotbunnies.info/articles/extensions.html RabbitMQ Extensions guide
31
+ # @api public
11
32
  def initialize(channel, name, options={})
12
33
  @channel = channel
13
34
  @name = name
14
35
  @options = {:durable => false, :exclusive => false, :auto_delete => false, :passive => false, :arguments => Hash.new}.merge(options)
36
+
37
+ @durable = @options[:durable]
38
+ @exclusive = @options[:exclusive]
39
+ @server_named = @name.empty?
40
+ @auto_delete = @options[:auto_delete]
41
+ @arguments = @options[:arguments]
42
+
43
+ @bindings = Set.new
15
44
  end
16
45
 
46
+
47
+ # @return [Boolean] true if this queue was declared as durable (will survive broker restart).
48
+ # @api public
49
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
50
+ def durable?
51
+ @durable
52
+ end # durable?
53
+
54
+ # @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer)
55
+ # @api public
56
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
57
+ def exclusive?
58
+ @exclusive
59
+ end # exclusive?
60
+
61
+ # @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds).
62
+ # @api public
63
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
64
+ def auto_delete?
65
+ @auto_delete
66
+ end # auto_delete?
67
+
68
+ # @return [Boolean] true if this queue was declared as server named.
69
+ # @api public
70
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
71
+ def server_named?
72
+ @server_named
73
+ end # server_named?
74
+
75
+ # @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins)
76
+ # @api public
77
+ def arguments
78
+ @arguments
79
+ end
80
+
81
+
82
+
83
+ # Binds queue to an exchange
84
+ #
85
+ # @param [HotBunnies::Exchange,String] exchange Exchange to bind to
86
+ # @param [Hash] options Binding properties
87
+ #
88
+ # @option options [String] :routing_key Routing key
89
+ # @option options [Hash] :arguments ({}) Additional optional binding arguments
90
+ #
91
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
92
+ # @see http://hotbunnies.info/articles/bindings.html Bindings guide
93
+ # @api public
17
94
  def bind(exchange, options={})
18
- exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end
19
- @channel.queue_bind(@name, exchange_name, options.fetch(:routing_key, ''), options[:arguments])
95
+ exchange_name = if exchange.respond_to?(:name) then
96
+ exchange.name
97
+ else
98
+ exchange.to_s
99
+ end
100
+ @channel.queue_bind(@name, exchange_name, (options[:routing_key] || options[:key] || ""), options[:arguments])
101
+
102
+ # store bindings for automatic recovery. We need to be very careful to
103
+ # not cause an infinite rebinding loop here when we recover. MK.
104
+ binding = { :exchange => exchange_name, :routing_key => (options[:routing_key] || options[:key]), :arguments => options[:arguments] }
105
+ @bindings << binding unless @bindings.include?(binding)
20
106
 
21
107
  self
22
108
  end
23
109
 
110
+ # Unbinds queue from an exchange
111
+ #
112
+ # @param [HotBunnies::Exchange,String] exchange Exchange to unbind from
113
+ # @param [Hash] options Binding properties
114
+ #
115
+ # @option options [String] :routing_key Routing key
116
+ # @option options [Hash] :arguments ({}) Additional optional binding arguments
117
+ #
118
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
119
+ # @see http://hotbunnies.info/articles/bindings.html Bindings guide
120
+ # @api public
24
121
  def unbind(exchange, options={})
25
- exchange_name = if exchange.respond_to?(:name) then exchange.name else exchange.to_s end
122
+ exchange_name = if exchange.respond_to?(:name) then
123
+ exchange.name
124
+ else
125
+ exchange.to_s
126
+ end
26
127
  @channel.queue_unbind(@name, exchange_name, options.fetch(:routing_key, ''))
27
128
 
129
+ binding = { :exchange => exchange_name, :routing_key => (options[:routing_key] || options[:key] || ""), :arguments => options[:arguments] }
130
+ @bindings.delete(binding) unless @bindings.include?(binding)
131
+
28
132
  self
29
133
  end
30
134
 
31
- def delete
32
- @channel.queue_delete(@name)
135
+ # Deletes the queue
136
+ #
137
+ # @option [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers?
138
+ # @option [Boolean] if_empty (false) Should this queue be deleted only if it has no messages?
139
+ #
140
+ # @see http://hotbunnies.info/articles/queues.html Queues and Consumers guide
141
+ # @api public
142
+ def delete(if_unused = false, if_empty = false)
143
+ @channel.queue_delete(@name, if_unused, if_empty)
33
144
  end
34
145
 
35
146
  def purge
@@ -49,9 +160,13 @@ module HotBunnies
49
160
 
50
161
  def build_consumer(opts, &block)
51
162
  if opts[:block] || opts[:blocking]
52
- BlockingCallbackConsumer.new(@channel, opts[:buffer_size], opts, block)
163
+ BlockingCallbackConsumer.new(@channel, self, opts[:buffer_size], opts, block)
53
164
  else
54
- AsyncCallbackConsumer.new(@channel, opts, block, opts.fetch(:executor, JavaConcurrent::Executors.new_single_thread_executor))
165
+ esf = opts.fetch(:executor_factory, Proc.new {
166
+ JavaConcurrent::Executors.new_single_thread_executor
167
+ })
168
+ es = opts.fetch(:executor, esf.call)
169
+ AsyncCallbackConsumer.new(@channel, self, opts.merge(:executor => es, :executor_factory => esf), block)
55
170
  end
56
171
  end
57
172
 
@@ -117,5 +232,35 @@ module HotBunnies
117
232
  end
118
233
  @name = response.queue
119
234
  end
235
+
236
+ # @private
237
+ def recover_from_network_failure
238
+ if self.server_named?
239
+ old_name = @name.dup
240
+ @name = ""
241
+
242
+ @channel.deregister_queue_named(old_name)
243
+ end
244
+
245
+ # puts "Recovering queue #{@name}"
246
+ begin
247
+ declare!
248
+
249
+ @channel.register_queue(self)
250
+ rescue Exception => e
251
+ # TODO: use a logger
252
+ puts "Caught #{e.inspect} while redeclaring and registering #{@name}!"
253
+ end
254
+ recover_bindings
255
+ end
256
+
257
+ # @private
258
+ def recover_bindings
259
+ @bindings.each do |b|
260
+ # TODO: use a logger
261
+ # puts "Recovering binding #{b.inspect}"
262
+ self.bind(b[:exchange], b)
263
+ end
264
+ end
120
265
  end # Queue
121
266
  end # HotBunnies
@@ -1,10 +1,12 @@
1
1
  # encoding: utf-8
2
2
  require "hot_bunnies/shutdown_listener"
3
+ require "set"
3
4
 
4
5
  module HotBunnies
5
6
  java_import com.rabbitmq.client.ConnectionFactory
6
7
  java_import com.rabbitmq.client.Connection
7
8
  java_import java.util.concurrent.ConcurrentHashMap
9
+ java_import java.util.concurrent.ConcurrentSkipListSet
8
10
 
9
11
  # Connection to a RabbitMQ node.
10
12
  #
@@ -22,6 +24,9 @@ module HotBunnies
22
24
  # API
23
25
  #
24
26
 
27
+ # Default reconnection interval for TCP connection failures
28
+ DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
29
+
25
30
  # Connects to a RabbitMQ node.
26
31
  #
27
32
  # @param [Hash] options Connection options
@@ -65,7 +70,7 @@ module HotBunnies
65
70
  end
66
71
 
67
72
 
68
- new(cf)
73
+ new(cf, options)
69
74
  end
70
75
 
71
76
  # @private
@@ -75,14 +80,26 @@ module HotBunnies
75
80
 
76
81
 
77
82
  # @private
78
- def initialize(connection_factory)
83
+ def initialize(connection_factory, opts = {})
79
84
  @cf = connection_factory
80
85
  @connection = converting_rjc_exceptions_to_ruby do
81
86
  self.new_connection
82
87
  end
83
88
  @channels = ConcurrentHashMap.new
84
-
85
89
  @thread = Thread.current
90
+
91
+ # should automatic recovery from network failures be used?
92
+ @automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil?
93
+ true
94
+ else
95
+ opts[:automatically_recover] || opts[:automatic_recovery]
96
+ end
97
+ @network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
98
+ @shutdown_hooks = ConcurrentSkipListSet.new
99
+
100
+ if @automatically_recover
101
+ self.add_automatic_recovery_hook
102
+ end
86
103
  end
87
104
 
88
105
  # Opens a new channel.
@@ -115,13 +132,57 @@ module HotBunnies
115
132
  @connection.close
116
133
  end
117
134
 
135
+ def open?
136
+ @connection.open?
137
+ end
138
+ alias connected? open?
139
+
118
140
  def on_shutdown(&block)
119
141
  sh = ShutdownListener.new(self, &block)
142
+ @shutdown_hooks << sh
143
+
120
144
  @connection.add_shutdown_listener(sh)
121
145
 
122
146
  sh
123
147
  end
124
148
 
149
+ # @private
150
+ def add_automatic_recovery_hook
151
+ fn = Proc.new do |_, signal|
152
+ if !signal.initiated_by_application
153
+ self.automatically_recover
154
+ end
155
+ end
156
+
157
+ @automatic_recovery_hook = self.on_shutdown(&fn)
158
+ end
159
+
160
+ # @private
161
+ def disable_automatic_recovery
162
+ @connetion.remove_shutdown_listener(@automatic_recovery_hook) if @automatic_recovery_hook
163
+ end
164
+
165
+ def automatically_recover
166
+ # recovering immediately makes little sense. Wait a bit first. MK.
167
+ java.lang.Thread.sleep(@network_recovery_interval * 1000)
168
+
169
+ @connection = converting_rjc_exceptions_to_ruby do
170
+ self.new_connection
171
+ end
172
+ self.recover_shutdown_hooks
173
+
174
+ @channels.each do |id, ch|
175
+ ch.automatically_recover(self, @connection)
176
+ end
177
+ end
178
+
179
+ # @private
180
+ def recover_shutdown_hooks
181
+ @shutdown_hooks.each do |sh|
182
+ @connection.add_shutdown_listener(sh)
183
+ end
184
+ end
185
+
125
186
  # Flushes the socket used by this connection.
126
187
  def flush
127
188
  @connection.flush
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module HotBunnies
4
- VERSION = "2.0.0.pre9"
4
+ VERSION = "2.0.0.pre10"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hot_bunnies
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre9
4
+ version: 2.0.0.pre10
5
5
  prerelease: 6
6
6
  platform: java
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-07-11 00:00:00.000000000 Z
13
+ date: 2013-08-11 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: RabbitMQ client for JRuby built around the official RabbitMQ Java client
16
16
  email: