right_amqp 0.3.3 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -247,7 +247,13 @@ module AMQP
247
247
  def reconnect force = false
248
248
  if @reconnecting and not force
249
249
  # Wait after first reconnect attempt and in between each subsequent attempt
250
- EM.add_timer(@settings[:reconnect_interval] || 5) { reconnect(true) }
250
+ EM.add_timer(@settings[:reconnect_interval] || 5) do
251
+ begin
252
+ reconnect(true)
253
+ rescue Exception => e
254
+ logger.exception("[amqp] Failed to reconnect", e, :trace)
255
+ end
256
+ end
251
257
  return
252
258
  end
253
259
 
@@ -265,7 +271,13 @@ module AMQP
265
271
  again = again.call if again.is_a?(Proc)
266
272
  if again.is_a?(Numeric)
267
273
  # Wait before making initial reconnect attempt
268
- EM.add_timer(again) { reconnect(true) }
274
+ EM.add_timer(again) do
275
+ begin
276
+ reconnect(true)
277
+ rescue Exception => e
278
+ logger.exception("[amqp] Failed to reconnect", e, :trace)
279
+ end
280
+ end
269
281
  return
270
282
  elsif ![nil, true].include?(again)
271
283
  raise ::AMQP::Error, "Could not interpret :reconnect_delay => #{again.inspect}; expected nil, true, or Numeric"
@@ -275,9 +287,6 @@ module AMQP
275
287
  log 'reconnecting'
276
288
  logger.info("[amqp] Attempting to reconnect to #{@settings[:identity]}")
277
289
  EM.reconnect(@settings[:host], @settings[:port], self)
278
- rescue Exception => e
279
- logger.exception("[amqp] Failed to reconnect", e, :trace)
280
- failed
281
290
  end
282
291
 
283
292
  def self.connect opts = {}
@@ -291,14 +300,14 @@ module AMQP
291
300
 
292
301
  def failed
293
302
  @connection_status.call(:failed) if @connection_status
294
- @has_failed = true
303
+ @failed = true
295
304
  close_connection
296
305
  end
297
306
 
298
307
  private
299
308
 
300
309
  def disconnected
301
- unless @has_failed
310
+ unless @failed
302
311
  @connection_status.call(:disconnected) if @connection_status
303
312
  reconnect
304
313
  end
@@ -65,10 +65,10 @@ module RightAMQP
65
65
  attr_reader :last_failed
66
66
 
67
67
  # (RightSupport::Stats::Activity) AMQP lost connection statistics
68
- attr_reader :disconnects
68
+ attr_reader :disconnect_stats
69
69
 
70
70
  # (RightSupport::Stats::Activity) AMQP connection failure statistics
71
- attr_reader :failures
71
+ attr_reader :failure_stats
72
72
 
73
73
  # (Integer) Number of attempts to connect after failure
74
74
  attr_reader :retries
@@ -83,7 +83,10 @@ module RightAMQP
83
83
  # :index(String):: Unique index for broker within set of brokers for use in forming alias
84
84
  # serializer(Serializer):: Serializer used for unmarshaling received messages to packets
85
85
  # (responds to :load); if nil, has same effect as setting subscribe option :no_unserialize
86
- # exceptions(RightSupport::Stats::Exceptions):: Exception statistics container
86
+ # exception_stats(RightSupport::Stats::Exceptions):: Exception statistics container to be updated
87
+ # whenever there is an unexpected exception
88
+ # non_delivery_stats(RightSupport::Stats::Activity):: Non-delivery statistics container to be
89
+ # updated whenever a message cannot be sent or received
87
90
  # options(Hash):: Configuration options
88
91
  # :user(String):: User name
89
92
  # :pass(String):: Password
@@ -96,17 +99,28 @@ module RightAMQP
96
99
  # :prefetch(Integer):: Maximum number of messages the AMQP broker is to prefetch for the agent
97
100
  # before it receives an ack. Value 1 ensures that only last unacknowledged gets redelivered
98
101
  # if the agent crashes. Value 0 means unlimited prefetch.
102
+ # :fiber_pool(NB::FiberPool):: Pool of initialized fibers to be used for asynchronous message
103
+ # processing (can be overridden with subscribe option)
99
104
  # :exception_on_receive_callback(Proc):: Callback activated on a receive exception with parameters
100
105
  # message(Object):: Message received
101
106
  # exception(Exception):: Exception raised
102
107
  # :update_status_callback(Proc):: Callback activated on a connection status change with parameters
103
108
  # broker(BrokerClient):: Broker client
104
109
  # connected_before(Boolean):: Whether was connected prior to this status change
110
+ # :return_message_callback(Proc):: Callback activated when a message is returned with parameters
111
+ # to(String):: Queue to which message was published
112
+ # reason(String):: Reason for return
113
+ # "NO_ROUTE" - queue does not exist
114
+ # "NO_CONSUMERS" - queue exists but it has no consumers, or if :immediate was specified,
115
+ # all consumers are not immediately ready to consume
116
+ # "ACCESS_REFUSED" - queue not usable because broker is in the process of stopping service
117
+ # message(String):: Returned serialized message
118
+ #
105
119
  # existing(BrokerClient|nil):: Existing broker client for this address, or nil if none
106
120
  #
107
121
  # === Raise
108
122
  # ArgumentError:: If serializer does not respond to :dump and :load
109
- def initialize(identity, address, serializer, exceptions, options, existing = nil)
123
+ def initialize(identity, address, serializer, exception_stats, non_delivery_stats, options, existing = nil)
110
124
  @options = options
111
125
  @identity = identity
112
126
  @host = address[:host]
@@ -117,21 +131,22 @@ module RightAMQP
117
131
  unless serializer.nil? || [:dump, :load].all? { |m| serializer.respond_to?(m) }
118
132
  raise ArgumentError, "serializer must be a class/object that responds to :dump and :load"
119
133
  end
120
- @serializer = serializer
121
- @exceptions = exceptions
122
- @queues = []
123
- @last_failed = false
124
- @disconnects = RightSupport::Stats::Activity.new(measure_rate = false)
125
- @failures = RightSupport::Stats::Activity.new(measure_rate = false)
126
- @retries = 0
134
+ @serializer = serializer
135
+ @queues = []
136
+ @last_failed = false
137
+ @exception_stats = exception_stats
138
+ @non_delivery_stats = non_delivery_stats
139
+ @disconnect_stats = RightSupport::Stats::Activity.new(measure_rate = false)
140
+ @failure_stats = RightSupport::Stats::Activity.new(measure_rate = false)
141
+ @retries = 0
127
142
 
128
143
  connect(address, @options[:reconnect_interval])
129
144
 
130
145
  if existing
131
- @disconnects = existing.disconnects
132
- @failures = existing.failures
133
- @last_failed = existing.last_failed
134
- @retries = existing.retries
146
+ @disconnect_stats = existing.disconnect_stats
147
+ @failure_stats = existing.failure_stats
148
+ @last_failed = existing.last_failed
149
+ @retries = existing.retries
135
150
  update_failure if @status == :failed
136
151
  end
137
152
  end
@@ -174,22 +189,23 @@ module RightAMQP
174
189
  # Subscribe an AMQP queue to an AMQP exchange
175
190
  # Do not wait for confirmation from broker that subscription is complete
176
191
  # When a message is received, acknowledge, unserialize, and log it as specified
177
- # If the message is unserialized and it is not of the right type, it is dropped after logging a warning
192
+ # If the message is unserialized and it is not of the right type, it is dropped after logging an error
178
193
  #
179
194
  # === Parameters
180
195
  # queue(Hash):: AMQP queue being subscribed with keys :name and :options,
181
196
  # which are the standard AMQP ones plus
182
197
  # :no_declare(Boolean):: Whether to skip declaring this queue on the broker
183
- # to cause its creation; for use when client does not have permission to create or
198
+ # to cause its creation; for use when caller does not have permission to create or
184
199
  # knows the queue already exists and wants to avoid declare overhead
185
200
  # exchange(Hash|nil):: AMQP exchange to subscribe to with keys :type, :name, and :options,
186
201
  # nil means use empty exchange by directly subscribing to queue; the :options are the
187
202
  # standard AMQP ones plus
188
203
  # :no_declare(Boolean):: Whether to skip declaring this exchange on the broker
189
- # to cause its creation; for use when client does not have create permission or
204
+ # to cause its creation; for use when caller does not have create permission or
190
205
  # knows the exchange already exists and wants to avoid declare overhead
191
206
  # options(Hash):: Subscribe options:
192
- # :ack(Boolean):: Explicitly acknowledge received messages to AMQP
207
+ # :ack(Boolean):: Whether caller takes responsibility for explicitly acknowledging each
208
+ # message received, defaults to implicit acknowledgement in AMQP as part of message receipt
193
209
  # :no_unserialize(Boolean):: Do not unserialize message, this is an escape for special
194
210
  # situations like enrollment, also implicitly disables receive filtering and logging;
195
211
  # this option is implicitly invoked if initialize without a serializer
@@ -200,16 +216,22 @@ module RightAMQP
200
216
  # :no_log(Boolean):: Disable receive logging unless debug level
201
217
  # :exchange2(Hash):: Additional exchange to which same queue is to be bound
202
218
  # :brokers(Array):: Identity of brokers for which to subscribe, defaults to all usable if nil or empty
219
+ # :fiber_pool(NB::FiberPool):: Pool of initialized fibers to be used for asynchronous message
220
+ # processing (non-nil value will override constructor option setting)
203
221
  #
204
222
  # === Block
205
- # Block with following parameters to be called each time exchange matches a message to the queue:
223
+ # Required block with following parameters to be called each time exchange matches a message to the queue
206
224
  # identity(String):: Serialized identity of broker delivering the message
207
225
  # message(Packet|String):: Message received, which is unserialized unless :no_unserialize was specified
208
226
  # header(AMQP::Protocol::Header):: Message header (optional block parameter)
209
227
  #
228
+ # === Raise
229
+ # ArgumentError:: If a block is not supplied
230
+ #
210
231
  # === Return
211
232
  # (Boolean):: true if subscribe successfully or if already subscribed, otherwise false
212
- def subscribe(queue, exchange = nil, options = {}, &blk)
233
+ def subscribe(queue, exchange = nil, options = {}, &block)
234
+ raise ArgumentError, "Must call this method with a block" unless block
213
235
  return false unless usable?
214
236
  return true unless @queues.select { |q| q.name == queue[:name] }.empty?
215
237
 
@@ -235,52 +257,24 @@ module RightAMQP
235
257
  end
236
258
  q = binding
237
259
  end
238
- if options[:ack]
239
- q.subscribe(:ack => true) do |header, message|
240
- begin
241
- # Ack now before processing to avoid risk of duplication after a crash
242
- header.ack
243
- if options[:no_unserialize] || @serializer.nil?
244
- execute_callback(blk, @identity, message, header)
245
- elsif message == "nil"
246
- # This happens as part of connecting an instance agent to a broker prior to version 13
247
- logger.debug("RECV #{@alias} nil message ignored")
248
- elsif
249
- packet = receive(queue[:name], message, options)
250
- execute_callback(blk, @identity, packet, header) if packet
251
- end
252
- true
253
- rescue Exception => e
254
- logger.exception("Failed executing block for message from queue #{queue.inspect}#{to_exchange} " +
255
- "on broker #{@alias}", e, :trace)
256
- @exceptions.track("receive", e)
257
- false
258
- end
259
- end
260
- else
261
- q.subscribe do |header, message|
262
- begin
263
- if options[:no_unserialize] || @serializer.nil?
264
- execute_callback(blk, @identity, message, header)
265
- elsif message == "nil"
266
- # This happens as part of connecting an instance agent to a broker
267
- logger.debug("RECV #{@alias} nil message ignored")
268
- elsif
269
- packet = receive(queue[:name], message, options)
270
- execute_callback(blk, @identity, packet, header) if packet
271
- end
272
- true
273
- rescue Exception => e
274
- logger.exception("Failed executing block for message from queue #{queue.inspect}#{to_exchange} " +
275
- "on broker #{@alias}", e, :trace)
276
- @exceptions.track("receive", e)
277
- false
260
+ q.subscribe(options[:ack] ? {:ack => true} : {}) do |header, message|
261
+ begin
262
+ if pool = (options[:fiber_pool] || @options[:fiber_pool])
263
+ pool.spawn { receive(queue[:name], header, message, options, &block) }
264
+ else
265
+ receive(queue[:name], header, message, options, &block)
278
266
  end
267
+ rescue Exception => e
268
+ header.ack if options[:ack]
269
+ logger.exception("Failed setting up to receive message from queue #{queue.inspect} " +
270
+ "on broker #{@alias}", e, :trace)
271
+ @exception_stats.track("receive", e)
272
+ @non_delivery_stats.update("receive failure")
279
273
  end
280
274
  end
281
275
  rescue Exception => e
282
276
  logger.exception("Failed subscribing queue #{queue.inspect}#{to_exchange} on broker #{@alias}", e, :trace)
283
- @exceptions.track("subscribe", e)
277
+ @exception_stats.track("subscribe", e)
284
278
  false
285
279
  end
286
280
  end
@@ -296,17 +290,17 @@ module RightAMQP
296
290
  #
297
291
  # === Return
298
292
  # true:: Always return true
299
- def unsubscribe(queue_names, &blk)
293
+ def unsubscribe(queue_names, &block)
300
294
  if usable?
301
295
  @queues.each do |q|
302
296
  if queue_names.include?(q.name)
303
297
  begin
304
298
  logger.info("[stop] Unsubscribing queue #{q.name} on broker #{@alias}")
305
- q.unsubscribe { blk.call if blk }
299
+ q.unsubscribe { block.call if block }
306
300
  rescue Exception => e
307
301
  logger.exception("Failed unsubscribing queue #{q.name} on broker #{@alias}", e, :trace)
308
- @exceptions.track("unsubscribe", e)
309
- blk.call if blk
302
+ @exception_stats.track("unsubscribe", e)
303
+ block.call if block
310
304
  end
311
305
  end
312
306
  end
@@ -332,7 +326,7 @@ module RightAMQP
332
326
  true
333
327
  rescue Exception => e
334
328
  logger.exception("Failed declaring #{type.to_s} #{name} on broker #{@alias}", e, :trace)
335
- @exceptions.track("declare", e)
329
+ @exception_stats.track("declare", e)
336
330
  false
337
331
  end
338
332
  end
@@ -343,7 +337,7 @@ module RightAMQP
343
337
  # exchange(Hash):: AMQP exchange to subscribe to with keys :type, :name, and :options,
344
338
  # which are the standard AMQP ones plus
345
339
  # :no_declare(Boolean):: Whether to skip declaring this exchange or queue on the broker
346
- # to cause its creation; for use when client does not have create permission or
340
+ # to cause its creation; for use when caller does not have create permission or
347
341
  # knows the object already exists and wants to avoid declare overhead
348
342
  # :declare(Boolean):: Whether to delete this exchange or queue from the AMQP cache
349
343
  # to force it to be declared on the broker and thus be created if it does not exist
@@ -382,44 +376,12 @@ module RightAMQP
382
376
  true
383
377
  rescue Exception => e
384
378
  logger.exception("Failed publishing to exchange #{exchange.inspect} on broker #{@alias}", e, :trace)
385
- @exceptions.track("publish", e)
379
+ @exception_stats.track("publish", e)
380
+ @non_delivery_stats.update("publish failure")
386
381
  false
387
382
  end
388
383
  end
389
384
 
390
- # Provide callback to be activated when broker returns a message that could not be delivered
391
- # A message published with :mandatory => true is returned if the exchange does not have any associated queues
392
- # or if all the associated queues do not have any consumers
393
- # A message published with :immediate => true is returned for the same reasons as :mandatory plus if all
394
- # of the queues associated with the exchange are not immediately ready to consume the message
395
- #
396
- # === Block
397
- # Optional block with following parameters to be called when a message is returned
398
- # to(String):: Queue to which message was published
399
- # reason(String):: Reason for return
400
- # "NO_ROUTE" - queue does not exist
401
- # "NO_CONSUMERS" - queue exists but it has no consumers, or if :immediate was specified,
402
- # all consumers are not immediately ready to consume
403
- # "ACCESS_REFUSED" - queue not usable because broker is in the process of stopping service
404
- # message(String):: Returned serialized message
405
- #
406
- # === Return
407
- # true:: Always return true
408
- def return_message
409
- @channel.return_message do |info, message|
410
- begin
411
- to = if info.exchange && !info.exchange.empty? then info.exchange else info.routing_key end
412
- reason = info.reply_text
413
- logger.debug("RETURN #{@alias} because #{reason} for #{to}")
414
- yield(to, reason, message) if block_given?
415
- rescue Exception => e
416
- logger.exception("Failed return #{info.inspect} of message from broker #{@alias}", e, :trace)
417
- @exceptions.track("return", e)
418
- end
419
- end
420
- true
421
- end
422
-
423
385
  # Delete queue
424
386
  #
425
387
  # === Parameters
@@ -446,7 +408,7 @@ module RightAMQP
446
408
  end
447
409
  rescue Exception => e
448
410
  logger.exception("Failed deleting queue #{name.inspect} on broker #{@alias}", e, :trace)
449
- @exceptions.track("delete", e)
411
+ @exception_stats.track("delete", e)
450
412
  end
451
413
  end
452
414
  deleted
@@ -477,7 +439,7 @@ module RightAMQP
477
439
  #
478
440
  # === Return
479
441
  # true:: Always return true
480
- def close(propagate = true, normal = true, log = true, &blk)
442
+ def close(propagate = true, normal = true, log = true, &block)
481
443
  final_status = normal ? :closed : :failed
482
444
  if ![:closed, :failed].include?(@status)
483
445
  begin
@@ -489,7 +451,7 @@ module RightAMQP
489
451
  end
490
452
  rescue Exception => e
491
453
  logger.exception("Failed to close broker #{@alias}", e, :trace)
492
- @exceptions.track("close", e)
454
+ @exception_stats.track("close", e)
493
455
  @status = final_status
494
456
  yield if block_given?
495
457
  end
@@ -516,8 +478,8 @@ module RightAMQP
516
478
  :alias => @alias,
517
479
  :status => @status,
518
480
  :retries => @retries,
519
- :disconnects => @disconnects.total,
520
- :failures => @failures.total,
481
+ :disconnects => @disconnect_stats.total,
482
+ :failures => @failure_stats.total,
521
483
  }
522
484
  end
523
485
 
@@ -532,15 +494,16 @@ module RightAMQP
532
494
  # "disconnects"(Integer|nil):: Number of times lost connection, or nil if none
533
495
  # "failure last"(Hash|nil):: Last connect failure information with key "elapsed", or nil if none
534
496
  # "failures"(Integer|nil):: Number of failed attempts to connect to broker, or nil if none
497
+ # "retries"(Integer|nil):: Number of connect retries, or nil if none
535
498
  def stats
536
499
  {
537
500
  "alias" => @alias,
538
501
  "identity" => @identity,
539
502
  "status" => @status.to_s,
540
- "disconnect last" => @disconnects.last,
541
- "disconnects" => RightSupport::Stats.nil_if_zero(@disconnects.total),
542
- "failure last" => @failures.last,
543
- "failures" => RightSupport::Stats.nil_if_zero(@failures.total),
503
+ "disconnect last" => @disconnect_stats.last,
504
+ "disconnects" => RightSupport::Stats.nil_if_zero(@disconnect_stats.total),
505
+ "failure last" => @failure_stats.last,
506
+ "failures" => RightSupport::Stats.nil_if_zero(@failure_stats.total),
544
507
  "retries" => RightSupport::Stats.nil_if_zero(@retries)
545
508
  }
546
509
  end
@@ -570,7 +533,7 @@ module RightAMQP
570
533
  elsif status == :failed
571
534
  update_failure
572
535
  elsif status == :disconnected && before != :disconnected
573
- @disconnects.update
536
+ @disconnect_stats.update
574
537
  end
575
538
 
576
539
  unless status == before || @options[:update_status_callback].nil?
@@ -582,7 +545,7 @@ module RightAMQP
582
545
  protected
583
546
 
584
547
  # Connect to broker and register for status updates
585
- # Also set prefetch value if specified
548
+ # Also set prefetch value if specified and setup for message returns
586
549
  #
587
550
  # === Parameters
588
551
  # address(Hash):: Broker address
@@ -611,21 +574,67 @@ module RightAMQP
611
574
  @channel = MQ.new(@connection)
612
575
  @channel.__send__(:connection).connection_status { |status| update_status(status) }
613
576
  @channel.prefetch(@options[:prefetch]) if @options[:prefetch]
577
+ @channel.return_message { |header, message| handle_return(header, message) }
614
578
  rescue Exception => e
615
579
  @status = :failed
616
- @failures.update
580
+ @failure_stats.update
617
581
  logger.exception("Failed connecting to broker #{@alias}", e, :trace)
618
- @exceptions.track("connect", e)
582
+ @exception_stats.track("connect", e)
619
583
  @connection.close if @connection
620
584
  end
621
585
  end
622
586
 
623
- # Receive message by unserializing it, checking that it is an acceptable type, and logging accordingly
587
+ # Receive message by optionally unserializing it, passing it to the callback, and optionally
588
+ # acknowledging it
624
589
  #
625
590
  # === Parameters
626
591
  # queue(String):: Name of queue
592
+ # header(AMQP::Protocol::Header):: Message header
627
593
  # message(String):: Serialized packet
628
- # options(Hash):: Subscribe options:
594
+ # options(Hash):: Subscribe options
595
+ # :ack(Boolean):: Whether caller takes responsibility for explicitly acknowledging each
596
+ # message received, defaults to implicit acknowledgement in AMQP as part of message receipt
597
+ # :no_unserialize(Boolean):: Do not unserialize message, this is an escape for special
598
+ # situations like enrollment, also implicitly disables receive filtering and logging;
599
+ # this option is implicitly invoked if initialize without a serializer
600
+ #
601
+ # === Block
602
+ # Block with following parameters to be called with received message
603
+ # identity(String):: Serialized identity of broker delivering the message
604
+ # message(Packet|String):: Message received, which is unserialized unless :no_unserialize was specified
605
+ # header(AMQP::Protocol::Header):: Message header (optional block parameter)
606
+ #
607
+ # === Return
608
+ # true:: Always return true
609
+ def receive(queue, header, message, options, &block)
610
+ begin
611
+ if options[:no_unserialize] || @serializer.nil?
612
+ execute_callback(block, @identity, message, header)
613
+ elsif message == "nil"
614
+ # This happens as part of connecting an instance agent to a broker prior to version 13
615
+ header.ack if options[:ack]
616
+ logger.debug("RECV #{@alias} nil message ignored")
617
+ elsif packet = unserialize(queue, message, options)
618
+ execute_callback(block, @identity, packet, header)
619
+ elsif options[:ack]
620
+ # Need to ack empty packet since no callback is being made
621
+ header.ack
622
+ end
623
+ true
624
+ rescue Exception => e
625
+ header.ack if options[:ack]
626
+ logger.exception("Failed receiving message from queue #{queue.inspect} on broker #{@alias}", e, :trace)
627
+ @exception_stats.track("receive", e)
628
+ @non_delivery_stats.update("receive failure")
629
+ end
630
+ end
631
+
632
+ # Unserialize message, check that it is an acceptable type, and log it
633
+ #
634
+ # === Parameters
635
+ # queue(String):: Name of queue
636
+ # message(String):: Serialized packet
637
+ # options(Hash):: Subscribe options
629
638
  # (packet class)(Array(Symbol)):: Filters to be applied in to_s when logging packet to :info,
630
639
  # only packet classes specified are accepted, others are not processed but are logged with error
631
640
  # :category(String):: Packet category description to be used in error messages
@@ -634,7 +643,7 @@ module RightAMQP
634
643
  #
635
644
  # === Return
636
645
  # (Packet|nil):: Unserialized packet or nil if not of right type or if there is an exception
637
- def receive(queue, message, options = {})
646
+ def unserialize(queue, message, options = {})
638
647
  begin
639
648
  received_at = Time.now.to_f
640
649
  packet = @serializer.load(message)
@@ -648,15 +657,16 @@ module RightAMQP
648
657
  packet
649
658
  else
650
659
  category = options[:category] + " " if options[:category]
651
- logger.warning("Received invalid #{category}packet type from queue #{queue} on broker #{@alias}: #{packet.class}\n" + caller.join("\n"))
660
+ logger.error("Received invalid #{category}packet type from queue #{queue} on broker #{@alias}: #{packet.class}\n" + caller.join("\n"))
652
661
  nil
653
662
  end
654
663
  rescue Exception => e
655
664
  # TODO Taking advantage of Serializer knowledge here even though out of scope
656
665
  trace = e.class.name =~ /SerializationError/ ? :caller : :trace
657
- logger.exception("Failed receiving from queue #{queue} on #{@alias}", e, trace)
658
- @exceptions.track("receive", e)
666
+ logger.exception("Failed unserializing message from queue #{queue.inspect} on broker #{@alias}", e, trace)
667
+ @exception_stats.track("receive", e)
659
668
  @options[:exception_on_receive_callback].call(message, e) if @options[:exception_on_receive_callback]
669
+ @non_delivery_stats.update("receive failure")
660
670
  nil
661
671
  end
662
672
  end
@@ -682,7 +692,29 @@ module RightAMQP
682
692
  else
683
693
  @last_failed = true
684
694
  @retries = 0
685
- @failures.update
695
+ @failure_stats.update
696
+ end
697
+ true
698
+ end
699
+
700
+ # Handle message returned by broker because it could not deliver it
701
+ #
702
+ # === Parameters
703
+ # header(AMQP::Protocol::Header):: Message header
704
+ # message(String):: Serialized packet
705
+ #
706
+ # === Return
707
+ # true:: Always return true
708
+ def handle_return(header, message)
709
+ begin
710
+ to = if header.exchange && !header.exchange.empty? then header.exchange else header.routing_key end
711
+ reason = header.reply_text
712
+ callback = @options[:return_message_callback]
713
+ logger.__send__(callback ? :debug : :info, "RETURN #{@alias} for #{to} because #{reason}")
714
+ callback.call(@identity, to, reason, message) if callback
715
+ rescue Exception => e
716
+ logger.exception("Failed return #{header.inspect} of message from broker #{@alias}", e, :trace)
717
+ @exception_stats.track("return", e)
686
718
  end
687
719
  true
688
720
  end