amqp 0.8.0.rc13 → 0.8.0.rc14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/.rspec +2 -1
  2. data/.travis.yml +8 -2
  3. data/.yardopts +1 -0
  4. data/CHANGELOG +9 -0
  5. data/Gemfile +17 -11
  6. data/README.md +26 -16
  7. data/amqp.gemspec +2 -2
  8. data/bin/ci/before_build.sh +21 -0
  9. data/docs/08Migration.textile +199 -5
  10. data/docs/AMQP091ModelExplained.textile +322 -0
  11. data/docs/Bindings.textile +24 -4
  12. data/docs/Clustering.textile +1 -1
  13. data/docs/ConnectingToTheBroker.textile +98 -82
  14. data/docs/ConnectionEncryptionWithTLS.textile +65 -5
  15. data/docs/DocumentationGuidesIndex.textile +93 -13
  16. data/docs/Durability.textile +1 -1
  17. data/docs/ErrorHandling.textile +458 -94
  18. data/docs/Exchanges.textile +901 -87
  19. data/docs/GettingStarted.textile +278 -143
  20. data/docs/PatternsAndUseCases.textile +420 -0
  21. data/docs/Queues.textile +730 -178
  22. data/docs/RabbitMQVersions.textile +18 -3
  23. data/docs/RunningTests.textile +1 -1
  24. data/docs/TestingWithEventedSpec.textile +121 -0
  25. data/docs/Troubleshooting.textile +15 -1
  26. data/docs/VendorSpecificExtensions.textile +1 -1
  27. data/docs/diagrams/001_hello_world_example_routing.png +0 -0
  28. data/docs/diagrams/002_blabbr_example_routing.png +0 -0
  29. data/docs/diagrams/003_weathr_example_routing.png +0 -0
  30. data/docs/diagrams/004_fanout_exchange.png +0 -0
  31. data/docs/diagrams/005_direct_exchange.png +0 -0
  32. data/docs/diagrams/redhat/direct_exchange.png +0 -0
  33. data/docs/diagrams/redhat/fanout_exchange.png +0 -0
  34. data/docs/diagrams/redhat/topic_exchange.png +0 -0
  35. data/examples/error_handling/automatic_recovery_of_channel_and_queues.rb +50 -0
  36. data/examples/error_handling/automatically_recovering_hello_world_consumer.rb +51 -0
  37. data/examples/error_handling/automatically_recovering_hello_world_consumer_that_uses_a_server_named_queue.rb +51 -0
  38. data/examples/error_handling/basic_connection_failover.rb +22 -0
  39. data/examples/error_handling/channel_level_exception.rb +9 -2
  40. data/examples/error_handling/connection_level_exception.rb +8 -1
  41. data/examples/error_handling/connection_level_exception_with_objects.rb +49 -0
  42. data/examples/error_handling/connection_loss_handler.rb +1 -5
  43. data/examples/error_handling/hello_world_producer.rb +43 -0
  44. data/examples/error_handling/insufficient_permissions.rb +54 -0
  45. data/examples/error_handling/manual_connection_and_channel_recovery.rb +71 -0
  46. data/examples/error_handling/queue_exclusivity_violation.rb +41 -0
  47. data/examples/error_handling/queue_name_violation.rb +31 -0
  48. data/examples/exchanges/autodeletion_of_exchanges.rb +1 -4
  49. data/examples/guides/queues/01a_declaring_a_server_named_queue_using_queue_constructor.rb +7 -8
  50. data/examples/guides/queues/01b_declaring_a_queue_using_queue_constructor.rb +7 -8
  51. data/examples/guides/queues/02a_declaring_a_durable_shared_queue.rb +5 -8
  52. data/examples/guides/queues/02b_declaring_a_durable_shared_queue.rb +5 -8
  53. data/examples/guides/queues/03a_declaring_a_temporary_exclusive_queue.rb +7 -8
  54. data/examples/guides/queues/04_bind_a_queue_using_exchange_instance.rb +9 -10
  55. data/examples/guides/queues/05_bind_a_queue_using_exchange_name.rb +8 -10
  56. data/examples/guides/queues/06_subscribe_to_receive_messages.rb +10 -12
  57. data/examples/guides/queues/07_fetch_a_message_from_the_queue.rb +14 -14
  58. data/examples/guides/queues/08_unsubscribing_a_consumer.rb +13 -16
  59. data/examples/guides/queues/09_unbinding_from_exchange.rb +16 -22
  60. data/examples/guides/queues/10_purge_a_queue.rb +13 -18
  61. data/examples/guides/queues/11_deleting_a_queue.rb +14 -19
  62. data/examples/guides/queues/12_objects_that_consume_messages.rb +69 -0
  63. data/examples/guides/queues/13_objects_that_consume_messages_take_two.rb +89 -0
  64. data/examples/hello_world.rb +1 -3
  65. data/examples/hello_world_with_an_empty_string.rb +5 -6
  66. data/examples/inspecting_server_information.rb +45 -0
  67. data/examples/issues/issue_93.rb +23 -0
  68. data/examples/issues/issue_94.rb +23 -0
  69. data/examples/patterns/command/consumer.rb +45 -0
  70. data/examples/patterns/command/producer.rb +26 -0
  71. data/examples/patterns/request_reply/client.rb +29 -0
  72. data/examples/patterns/request_reply/server.rb +26 -0
  73. data/examples/publishing/publishing_a_one_off_message.rb +6 -4
  74. data/examples/publishing/returned_messages.rb +2 -10
  75. data/examples/queues/accessing_message_metadata.rb +15 -13
  76. data/examples/queues/queue_status.rb +12 -15
  77. data/examples/routing/fanout_routing.rb +33 -0
  78. data/examples/routing/headers_routing.rb +17 -15
  79. data/examples/routing/round_robin_with_direct_exchange.rb +39 -0
  80. data/examples/routing/round_robin_with_the_default_exchange.rb +38 -0
  81. data/examples/routing/unroutable_mandatory_message_is_returned.rb +33 -0
  82. data/examples/routing/weather_updates.rb +15 -20
  83. data/examples/tls/using_tls.rb +41 -0
  84. data/lib/amqp/bit_set.rb +80 -0
  85. data/lib/amqp/broker.rb +72 -0
  86. data/lib/amqp/channel.rb +93 -13
  87. data/lib/amqp/client.rb +11 -22
  88. data/lib/amqp/compatibility/ruby187_patchlevel_check.rb +2 -0
  89. data/lib/amqp/connection.rb +2 -3
  90. data/lib/amqp/consumer.rb +208 -0
  91. data/lib/amqp/deprecated/fork.rb +2 -0
  92. data/lib/amqp/deprecated/mq.rb +2 -0
  93. data/lib/amqp/exchange.rb +6 -4
  94. data/lib/amqp/extensions/rabbitmq.rb +3 -1
  95. data/lib/amqp/header.rb +76 -14
  96. data/lib/amqp/int_allocator.rb +96 -0
  97. data/lib/amqp/logger.rb +2 -0
  98. data/lib/amqp/queue.rb +242 -86
  99. data/lib/amqp/rpc.rb +2 -0
  100. data/lib/amqp/session.rb +169 -9
  101. data/lib/amqp/utilities/event_loop_helper.rb +2 -0
  102. data/lib/amqp/utilities/server_type.rb +2 -0
  103. data/lib/amqp/version.rb +2 -2
  104. data/lib/mq.rb +4 -2
  105. data/lib/mq/logger.rb +3 -1
  106. data/lib/mq/rpc.rb +3 -1
  107. data/spec/integration/authentication_spec.rb +17 -10
  108. data/spec/integration/automatic_binding_for_default_direct_exchange_spec.rb +1 -1
  109. data/spec/integration/automatic_recovery_predicate_spec.rb +68 -0
  110. data/spec/integration/basic_get_spec.rb +2 -1
  111. data/spec/integration/{extensions/basic_return_spec.rb → basic_return_spec.rb} +2 -1
  112. data/spec/integration/channel_level_exception_handling_spec.rb +53 -0
  113. data/spec/integration/connection_level_exception_handling_spec.rb +49 -0
  114. data/spec/integration/declare_and_immediately_bind_a_server_named_queue_spec.rb +38 -17
  115. data/spec/integration/declare_one_hundred_server_named_queues_spec.rb +44 -0
  116. data/spec/integration/direct_exchange_routing_spec.rb +125 -0
  117. data/spec/integration/exchange_declaration_spec.rb +75 -46
  118. data/spec/integration/extensions/rabbitmq/publisher_confirmations_spec.rb +180 -0
  119. data/spec/integration/{workload_distribution_spec.rb → fanout_exchange_routing_spec.rb} +10 -9
  120. data/spec/integration/headers_exchange_routing_spec.rb +269 -0
  121. data/spec/integration/hello_world_spec.rb +77 -0
  122. data/spec/integration/immediate_messages_spec.rb +59 -0
  123. data/spec/integration/mandatory_messages_spec.rb +52 -0
  124. data/spec/integration/message_metadata_access_spec.rb +106 -0
  125. data/spec/integration/multiple_consumers_per_queue_spec.rb +319 -0
  126. data/spec/integration/ordering_of_published_messages_spec.rb +96 -0
  127. data/spec/integration/queue_declaration_spec.rb +8 -8
  128. data/spec/integration/queue_status_spec.rb +66 -0
  129. data/spec/integration/recovery/per_channel_automatic_recovery_on_graceful_broker_shutdown_spec.rb +76 -0
  130. data/spec/integration/recovery/per_channel_automatic_recovery_spec.rb +72 -0
  131. data/spec/integration/redelivery_of_unacknowledged_messages_spec.rb +96 -0
  132. data/spec/integration/regressions/concurrent_publishing_on_the_same_channel_spec.rb +91 -0
  133. data/spec/integration/regressions/empty_message_body_spec.rb +56 -0
  134. data/spec/integration/regressions/issue66_spec.rb +2 -1
  135. data/spec/integration/reply_queue_communication_spec.rb +2 -1
  136. data/spec/integration/store_and_forward_spec.rb +4 -3
  137. data/spec/integration/topic_subscription_spec.rb +2 -1
  138. data/spec/integration/tx_commit_spec.rb +124 -0
  139. data/spec/integration/tx_rollback_spec.rb +167 -0
  140. data/spec/spec_helper.rb +44 -71
  141. data/spec/unit/amqp/bit_set_spec.rb +127 -0
  142. data/spec/unit/amqp/channel_id_allocation_spec.rb +40 -0
  143. data/spec/unit/amqp/connection_spec.rb +4 -2
  144. data/spec/unit/amqp/int_allocator_spec.rb +116 -0
  145. metadata +92 -26
  146. data/CONTRIBUTORS +0 -29
  147. data/docs/Routing.textile +0 -30
  148. data/examples/real-world/task-queue/README.textile +0 -3
  149. data/examples/real-world/task-queue/consumer.rb +0 -27
  150. data/examples/real-world/task-queue/producer.rb +0 -22
  151. data/spec/unit/amqp/basic_spec.rb +0 -39
  152. data/tasks.rb +0 -4
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+
3
+ require "amqp/bit_set"
4
+
5
+ module AMQP
6
+ # Simple bitset-based integer allocator, heavily inspired by com.rabbitmq.utility.IntAllocator class
7
+ # in the RabbitMQ Java client.
8
+ #
9
+ # Unlike monotonically incrementing identifier, this allocator is suitable for very long running programs
10
+ # that aggressively allocate and release channels.
11
+ class IntAllocator
12
+
13
+ #
14
+ # API
15
+ #
16
+
17
+ # @return [Integer] Number of integers in the allocation range
18
+ attr_reader :number_of_bits
19
+ # @return [Integer] Upper boundary of the integer range available for allocation
20
+ attr_reader :hi
21
+ # @return [Integer] Lower boundary of the integer range available for allocation
22
+ attr_reader :lo
23
+
24
+ # @param [Integer] lo Lower boundary of the integer range available for allocation
25
+ # @param [Integer] hi Upper boundary of the integer range available for allocation
26
+ # @raise [ArgumentError] if upper boundary is not greater than the lower one
27
+ def initialize(lo, hi)
28
+ raise ArgumentError.new "upper boundary must be greater than the lower one (given: hi = #{hi}, lo = #{lo})" unless hi > lo
29
+
30
+ @hi = hi
31
+ @lo = lo
32
+
33
+ @number_of_bits = hi - lo
34
+ @range = Range.new(1, @number_of_bits)
35
+ @free_set = BitSet.new(@number_of_bits)
36
+ end # initialize(hi, lo)
37
+
38
+ # Attempts to allocate next available integer. If allocation succeeds, allocated value is returned.
39
+ # Otherwise, nil is returned.
40
+ #
41
+ # Current implementation of this method is O(n), where n is number of bits in the range available for
42
+ # allocation.
43
+ #
44
+ # @return [Integer] Allocated integer if allocation succeeded. nil otherwise.
45
+ def allocate
46
+ if n = find_unallocated_position
47
+ @free_set.set(n)
48
+
49
+ n
50
+ else
51
+ -1
52
+ end
53
+ end # allocate
54
+
55
+ # Releases previously allocated integer. If integer provided as argument was not previously allocated,
56
+ # this method has no effect.
57
+ #
58
+ # @return [NilClass] nil
59
+ def free(reservation)
60
+ @free_set.unset(reservation)
61
+ end # free(reservation)
62
+ alias release free
63
+
64
+ # @return [Boolean] true if provided argument was previously allocated, false otherwise
65
+ def allocated?(reservation)
66
+ @free_set.get(reservation)
67
+ end # allocated?(reservation)
68
+
69
+ # Releases the whole allocation range
70
+ def reset
71
+ @free_set.clear
72
+ end # reset
73
+
74
+
75
+
76
+ protected
77
+
78
+ # This implementation is significantly less efficient
79
+ # that what the RabbitMQ Java client has (based on java.lang.Long#nextSetBit and
80
+ # java.lang.Long.numberOfTrailingZeros, and thus binary search over bits).
81
+ # But for channel id generation, this is a good enough implementation.
82
+ #
83
+ # @private
84
+ def find_unallocated_position
85
+ r = nil
86
+ @range.each do |i|
87
+ if !@free_set.get(i)
88
+ r = i
89
+ break;
90
+ end
91
+ end
92
+
93
+ r
94
+ end # find_unallocated_position
95
+ end # IntAllocator
96
+ end # AMQP
data/lib/amqp/logger.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  $stdout.puts <<-MESSAGE
2
4
  -------------------------------------------------------------------------------------
3
5
  DEPRECATION WARNING!
data/lib/amqp/queue.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require "amq/client/queue"
4
+ require "amqp/consumer"
4
5
 
5
6
  module AMQP
6
7
  # h2. What are AMQP queues?
@@ -173,15 +174,12 @@ module AMQP
173
174
  raise ArgumentError.new("queue name must not be nil; if you want broker to generate queue name for you, pass an empty string") if name.nil?
174
175
 
175
176
  @channel = channel
176
- name = AMQ::Protocol::EMPTY_STRING if name.nil?
177
177
  @name = name unless name.empty?
178
178
  @server_named = name.empty?
179
179
  @opts = self.class.add_default_options(name, opts, block)
180
180
 
181
181
  raise ArgumentError.new("server-named queues (name = '') declaration with :nowait => true makes no sense. If you are not sure what that means, simply drop :nowait => true from opts.") if @server_named && @opts[:nowait]
182
182
 
183
- @bindings = Hash.new
184
-
185
183
  # a deferrable that we use to delay operations until this queue is actually declared.
186
184
  # one reason for this is to support a case when a server-named queue is immediately bound.
187
185
  # it's crazy, but 0.7.x supports it, so... MK.
@@ -218,6 +216,15 @@ module AMQP
218
216
  end
219
217
  end
220
218
 
219
+ # Defines a callback that will be executed once queue is declared. More than one callback can be defined.
220
+ # if queue is already declared, given callback is executed immediately.
221
+ #
222
+ # @api public
223
+ def once_declared(&block)
224
+ @declaration_deferrable.callback(&block)
225
+ end # once_declared(&block)
226
+
227
+
221
228
  # @return [Boolean] true if this queue is server-named
222
229
  def server_named?
223
230
  @server_named
@@ -274,12 +281,6 @@ module AMQP
274
281
  # @api public
275
282
  # @see Queue#unbind
276
283
  def bind(exchange, opts = {}, &block)
277
- @status = :unbound
278
- # amq-client's Queue already does exchange.respond_to?(:name) ? exchange.name : exchange
279
- # for us
280
- exchange = exchange
281
- @bindings[exchange] = opts
282
-
283
284
  if self.server_named?
284
285
  @channel.once_open do
285
286
  @declaration_deferrable.callback do
@@ -296,6 +297,44 @@ module AMQP
296
297
  end
297
298
 
298
299
 
300
+ # @group Error Handling and Recovery
301
+
302
+ # Used by automatic recovery machinery.
303
+ # @private
304
+ # @api plugin
305
+ def rebind(&block)
306
+ @bindings.each { |b| self.bind(b[:exchange], b) }
307
+ end
308
+
309
+ # Called by associated connection object when AMQP connection has been re-established
310
+ # (for example, after a network failure).
311
+ #
312
+ # @api plugin
313
+ def auto_recover
314
+ self.exec_callback_yielding_self(:before_recovery)
315
+
316
+ if self.server_named?
317
+ old_name = @name.dup
318
+ @name = AMQ::Protocol::EMPTY_STRING
319
+
320
+ @channel.queues.delete(old_name)
321
+ end
322
+
323
+ self.redeclare do
324
+ @declaration_deferrable.succeed
325
+ self.rebind
326
+
327
+ @consumers.each { |tag, consumer| consumer.auto_recover }
328
+
329
+ self.exec_callback_yielding_self(:after_recovery)
330
+ end
331
+ end # auto_recover
332
+
333
+ # @endgroup
334
+
335
+
336
+
337
+
299
338
  # Remove the binding between the queue and exchange. The queue will
300
339
  # not receive any more messages until it is bound to another
301
340
  # exchange.
@@ -390,44 +429,18 @@ module AMQP
390
429
  # application where synchronous functionality is more important than
391
430
  # performance.
392
431
  #
393
- # If provided block takes one argument, it is passed message payload every time {Queue#pop} is called.
432
+ # If queue is empty, `payload` callback argument will be nil, otherwise arguments
433
+ # are identical to those of {AMQP::Queue#subscribe} callback.
394
434
  #
395
- # @example Use of callback with a single argument
435
+ # @example Fetching messages off AMQP queue on demand
396
436
  #
397
- # EM.run do
398
- # exchange = AMQP::Channel.direct("foo queue")
399
- # EM.add_periodic_timer(1) do
400
- # exchange.publish("random number #{rand(1000)}")
401
- # end
402
- #
403
- # # note that #bind is never called; it is implicit because
404
- # # the exchange and queue names match
405
- # queue = AMQP::Channel.queue('foo queue')
406
- # queue.pop { |body| puts "received payload [#{body}]" }
407
- #
408
- # EM.add_periodic_timer(1) { queue.pop }
409
- # end
410
- #
411
- # If the block takes 2 parameters, both the header and the body will
412
- # be passed in for processing. The header object is defined by
413
- # AMQP::Protocol::Header.
414
- #
415
- # @example Use of callback with two arguments
416
- #
417
- # EM.run do
418
- # exchange = AMQP::Channel.direct("foo queue")
419
- # EM.add_periodic_timer(1) do
420
- # exchange.publish("random number #{rand(1000)}")
421
- # end
422
- #
423
- # queue = AMQP::Channel.queue('foo queue')
424
- # queue.pop do |header, body|
425
- # p header
426
- # puts "received payload [#{body}]"
427
- # end
428
- #
429
- # EM.add_periodic_timer(1) { queue.pop }
430
- # end
437
+ # queue.pop do |metadata, payload|
438
+ # if payload
439
+ # puts "Fetched a message: #{payload.inspect}, content_type: #{metadata.content_type}. Shutting down..."
440
+ # else
441
+ # puts "No messages in the queue"
442
+ # end
443
+ # end
431
444
  #
432
445
  # @option opts [Boolean] :ack (false) If this field is set to false the server does not expect acknowledgments
433
446
  # for messages. That is, when a message is delivered to the client
@@ -479,6 +492,12 @@ module AMQP
479
492
  # exchange matches a message to this queue.
480
493
  #
481
494
  #
495
+ # Attempts to {Queue#subscribe} multiple times to the same exchange will raise an
496
+ # Exception. If you need more than one consumer per queue, use {AMQP::Consumer} instead.
497
+ # {file:docs/Queues.textile Documentation guide on queues} explains this and other topics
498
+ # in great detail.
499
+ #
500
+ #
482
501
  # @example Use of callback with a single argument
483
502
  #
484
503
  # EventMachine.run do
@@ -492,8 +511,7 @@ module AMQP
492
511
  # end
493
512
  #
494
513
  # If the block takes 2 parameters, both the header and the body will
495
- # be passed in for processing. The header object is defined by
496
- # AMQP::Protocol::Header.
514
+ # be passed in for processing.
497
515
  #
498
516
  # @example Use of callback with two arguments
499
517
  #
@@ -548,6 +566,120 @@ module AMQP
548
566
  # :timestamp => Time.now.to_i)
549
567
  # end
550
568
  #
569
+ # @example Using object as consumer (message handler), take one
570
+ #
571
+ # class Consumer
572
+ #
573
+ # #
574
+ # # API
575
+ # #
576
+ #
577
+ # def initialize(channel, queue_name = AMQ::Protocol::EMPTY_STRING)
578
+ # @queue_name = queue_name
579
+ #
580
+ # @channel = channel
581
+ # # Consumer#handle_channel_exception will handle channel
582
+ # # exceptions. Keep in mind that you can only register one error handler,
583
+ # # so the last one registered "wins".
584
+ # @channel.on_error(&method(:handle_channel_exception))
585
+ # end # initialize
586
+ #
587
+ # def start
588
+ # @queue = @channel.queue(@queue_name, :exclusive => true)
589
+ # # #handle_message method will be handling messages routed to @queue
590
+ # @queue.subscribe(&method(:handle_message))
591
+ # end # start
592
+ #
593
+ #
594
+ #
595
+ # #
596
+ # # Implementation
597
+ # #
598
+ #
599
+ # def handle_message(metadata, payload)
600
+ # puts "Received a message: #{payload}, content_type = #{metadata.content_type}"
601
+ # end # handle_message(metadata, payload)
602
+ #
603
+ # def handle_channel_exception(channel, channel_close)
604
+ # puts "Oops... a channel-level exception: code = #{channel_close.reply_code}, message = #{channel_close.reply_text}"
605
+ # end # handle_channel_exception(channel, channel_close)
606
+ # end
607
+ #
608
+ #
609
+ # @example Using object as consumer (message handler), take two: aggregatied handler
610
+ # class Consumer
611
+ #
612
+ # #
613
+ # # API
614
+ # #
615
+ #
616
+ # def handle_message(metadata, payload)
617
+ # puts "Received a message: #{payload}, content_type = #{metadata.content_type}"
618
+ # end # handle_message(metadata, payload)
619
+ # end
620
+ #
621
+ #
622
+ # class Worker
623
+ #
624
+ # #
625
+ # # API
626
+ # #
627
+ #
628
+ #
629
+ # def initialize(channel, queue_name = AMQ::Protocol::EMPTY_STRING, consumer = Consumer.new)
630
+ # @queue_name = queue_name
631
+ #
632
+ # @channel = channel
633
+ # @channel.on_error(&method(:handle_channel_exception))
634
+ #
635
+ # @consumer = consumer
636
+ # end # initialize
637
+ #
638
+ # def start
639
+ # @queue = @channel.queue(@queue_name, :exclusive => true)
640
+ # @queue.subscribe(&@consumer.method(:handle_message))
641
+ # end # start
642
+ #
643
+ #
644
+ #
645
+ # #
646
+ # # Implementation
647
+ # #
648
+ #
649
+ # def handle_channel_exception(channel, channel_close)
650
+ # puts "Oops... a channel-level exception: code = #{channel_close.reply_code}, message = #{channel_close.reply_text}"
651
+ # end # handle_channel_exception(channel, channel_close)
652
+ # end
653
+ #
654
+ # @example Unit-testing objects that are used as consumers, RSpec style
655
+ #
656
+ # require "ostruct"
657
+ # require "json"
658
+ #
659
+ # # RSpec example
660
+ # describe Consumer do
661
+ # describe "when a new message arrives" do
662
+ # subject { described_class.new }
663
+ #
664
+ # let(:metadata) do
665
+ # o = OpenStruct.new
666
+ #
667
+ # o.content_type = "application/json"
668
+ # o
669
+ # end
670
+ # let(:payload) { JSON.encode({ :command => "reload_config" }) }
671
+ #
672
+ # it "does some useful work" do
673
+ # # check preconditions here if necessary
674
+ #
675
+ # subject.handle_message(metadata, payload)
676
+ #
677
+ # # add your code expectations here
678
+ # end
679
+ # end
680
+ # end
681
+ #
682
+ #
551
683
  #
552
684
  # @option opts [Boolean ]:ack (false) If this field is set to false the server does not expect acknowledgments
553
685
  # for messages. That is, when a message is delivered to the client
@@ -580,51 +712,61 @@ module AMQP
580
712
  #
581
713
  # @see file:docs/Queues.textile Documentation guide on queues
582
714
  # @see #unsubscribe
715
+ # @see AMQP::Consumer
583
716
  def subscribe(opts = {}, &block)
584
- raise Error, 'already subscribed to the queue' if @consumer_tag
585
-
586
- # having initial value for @consumer_tag makes a lot of obscure issues
587
- # go away. It is set to real value once we receive consume-ok (it is handled by
588
- # AMQ::Client::Queue we inherit from).
589
- @consumer_tag = "for now"
717
+ raise RuntimeError.new("This queue already has default consumer. Please instantiate AMQP::Consumer directly to register additional consumers.") if @default_consumer
590
718
 
591
719
  opts[:nowait] = false if (@on_confirm_subscribe = opts[:confirm])
592
720
 
593
- # We have to maintain this multiple arities jazz
594
- # because older versions this gem are used in examples in at least 3
595
- # books published by O'Reilly :(. MK.
596
- delivery_shim = Proc.new { |method, headers, payload|
597
- case block.arity
598
- when 1 then
599
- block.call(payload)
600
- when 2 then
601
- h = Header.new(@channel, method, headers.decode_payload)
602
- block.call(h, payload)
603
- else
604
- h = Header.new(@channel, method, headers.decode_payload)
605
- block.call(h, payload, method.consumer_tag, method.delivery_tag, method.redelivered, method.exchange, method.routing_key)
606
- end
607
- }
608
-
609
721
  @channel.once_open do
610
- @consumer_tag = nil
611
- # consumer_tag is set by AMQ::Client::Queue once we receive consume-ok, this takes a while.
612
- self.consume(!opts[:ack], opts[:exclusive], (opts[:nowait] || block.nil?), opts[:no_local], nil, &opts[:confirm])
722
+ self.once_declared do
723
+ self.consume(!opts[:ack], opts[:exclusive], (opts[:nowait] || block.nil?), opts[:no_local], nil, &opts[:confirm])
724
+
725
+ self.on_delivery(&block)
726
+ end
613
727
  end
614
- self.on_delivery(&delivery_shim)
615
728
 
616
729
  self
617
730
  end
618
731
 
732
+ # @return [String] Consumer tag of the default consumer associated with this queue (if any), or nil
733
+ # @note Default consumer is the one registered with the convenience {AMQP::Queue#subscribe} method. It has no special properties of any kind.
734
+ # @see Queue#subscribe
735
+ # @see AMQP::Consumer
736
+ # @api public
737
+ def consumer_tag
738
+ if @default_consumer
739
+ @default_consumer.consumer_tag
740
+ else
741
+ nil
742
+ end
743
+ end # consumer_tag
619
744
 
620
- # Removes the subscription from the queue and cancels the consumer.
621
- # New messages will not be received by this queue instance.
622
- #
623
- # Due to the asynchronous nature of the protocol, it is possible for
624
- # "in flight" messages to be received after this call completes.
745
+ # @return [AMQP::Consumer] Default consumer associated with this queue (if any), or nil
746
+ # @note Default consumer is the one registered with the convenience {AMQP::Queue#subscribe} method. It has no special properties of any kind.
747
+ # @see Queue#subscribe
748
+ # @see AMQP::Consumer
749
+ # @api public
750
+ def default_consumer
751
+ @default_consumer
752
+ end
753
+
754
+
755
+ # @return [Class]
756
+ # @private
757
+ def self.consumer_class
758
+ AMQP::Consumer
759
+ end # self.consumer_class
760
+
761
+
762
+ # Removes the subscription from the queue and cancels the consumer. Once consumer is cancelled,
763
+ # messages will no longer be delivered to it, however, due to the asynchronous nature of the protocol, it is possible for
764
+ # “in flight” messages to be received after this call completes.
625
765
  # Those messages will be serviced by the last block used in a
626
766
  # {Queue#subscribe} or {Queue#pop} call.
627
767
  #
768
+ # Fetching messages with {AMQP::Queue#pop} is still possible even after consumer is cancelled.
769
+ #
628
770
  # Additionally, if the queue was created with _autodelete_ set to
629
771
  # true, the server will delete the queue after its wait period
630
772
  # has expired unless the queue is bound to an active exchange.
@@ -642,9 +784,13 @@ module AMQP
642
784
  #
643
785
  # @api public
644
786
  def unsubscribe(opts = {}, &block)
645
- # @consumer_tag is nillified for us by AMQ::Client::Queue, that is,
646
- # our superclass. MK.
647
- @channel.once_open { self.cancel(opts.fetch(:nowait, true), &block) }
787
+ @channel.once_open do
788
+ self.once_declared do
789
+ if @default_consumer
790
+ @default_consumer.cancel(opts.fetch(:nowait, true), &block); @default_consumer = nil
791
+ end
792
+ end
793
+ end
648
794
  end
649
795
 
650
796
  # Get the number of messages and active consumers (with active channel flow) on a queue.
@@ -670,16 +816,16 @@ module AMQP
670
816
 
671
817
 
672
818
  # Boolean check to see if the current queue has already subscribed
673
- # to messages delivery.
819
+ # to messages delivery (has default consumer).
674
820
  #
675
821
  # Attempts to {Queue#subscribe} multiple times to the same exchange will raise an
676
- # Exception. Only a single block at a time can be associated with any
677
- # queue instance for processing incoming messages.
822
+ # Exception. If you need more than one consumer per queue, use {AMQP::Consumer} instead.
678
823
  #
679
824
  # @return [Boolean] true if there is a consumer tag associated with this Queue instance
680
825
  # @api public
826
+ # @deprecated
681
827
  def subscribed?
682
- !!@consumer_tag
828
+ @default_consumer && @default_consumer.subscribed?
683
829
  end
684
830
 
685
831
 
@@ -688,7 +834,9 @@ module AMQP
688
834
  # @api public
689
835
  # @deprecated
690
836
  def callback
691
- @on_declare
837
+ return nil if !subscribed?
838
+
839
+ @default_consumer.callback
692
840
  end
693
841
 
694
842
 
@@ -711,6 +859,14 @@ module AMQP
711
859
  end
712
860
 
713
861
 
862
+ # @private
863
+ # @api plugin
864
+ def handle_connection_interruption(method = nil)
865
+ super(method)
866
+
867
+ @declaration_deferrable = EventMachine::DefaultDeferrable.new
868
+ end
869
+
714
870
  protected
715
871
 
716
872
  # @private