hot_bunnies 2.0.0.pre9-java → 2.0.0.pre10-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.
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: