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,39 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require "amqp"
10
+
11
+ EventMachine.run do
12
+ AMQP.connect do |connection|
13
+ channel1 = AMQP::Channel.new(connection)
14
+ channel2 = AMQP::Channel.new(connection)
15
+ exchange = channel1.direct("amqpgem.examples.exchanges.direct", :auto_delete => true)
16
+
17
+ q1 = channel1.queue("amqpgem.examples.queues.shared", :auto_delete => true).bind(exchange, :routing_key => "shared.key")
18
+ q1.subscribe do |payload|
19
+ puts "Queue #{q1.name} on channel 1 received #{payload}"
20
+ end
21
+
22
+ # since the queue is shared, binding here is redundant but we will leave it in for completeness.
23
+ q2 = channel2.queue("amqpgem.examples.queues.shared", :auto_delete => true).bind(exchange, :routing_key => "shared.key")
24
+ q2.subscribe do |payload|
25
+ puts "Queue #{q2.name} on channel 2 received #{payload}"
26
+ end
27
+
28
+ # Publish some test data in a bit, after all queues are declared & bound
29
+ EventMachine.add_timer(1.2) do
30
+ 5.times { |i| exchange.publish("Hello #{i}, direct exchanges world!", :routing_key => "shared.key") }
31
+ end
32
+
33
+
34
+ show_stopper = Proc.new { connection.close { EventMachine.stop } }
35
+
36
+ Signal.trap "TERM", show_stopper
37
+ EM.add_timer(3, show_stopper)
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require "amqp"
10
+
11
+ EventMachine.run do
12
+ AMQP.connect do |connection|
13
+ channel1 = AMQP::Channel.new(connection)
14
+ channel2 = AMQP::Channel.new(connection)
15
+ exchange = channel1.default_exchange
16
+
17
+ q1 = channel1.queue("amqpgem.examples.queues.shared", :auto_delete => true)
18
+ q1.subscribe do |payload|
19
+ puts "Queue #{q1.name} on channel 1 received #{payload}"
20
+ end
21
+
22
+ q2 = channel2.queue("amqpgem.examples.queues.shared", :auto_delete => true)
23
+ q2.subscribe do |payload|
24
+ puts "Queue #{q2.name} on channel 2 received #{payload}"
25
+ end
26
+
27
+ # Publish some test data in a bit, after all queues are declared & bound
28
+ EventMachine.add_timer(1.2) do
29
+ 5.times { |i| exchange.publish("Hello #{i}, direct exchanges world!", :routing_key => "amqpgem.examples.queues.shared") }
30
+ end
31
+
32
+
33
+ show_stopper = Proc.new { connection.close { EventMachine.stop } }
34
+
35
+ Signal.trap "TERM", show_stopper
36
+ EM.add_timer(3, show_stopper)
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ require 'amqp'
10
+
11
+ puts "=> Handling a returned unroutable message that was published as mandatory"
12
+ puts
13
+
14
+ AMQP.start(:host => '127.0.0.1') do |connection|
15
+ channel = AMQP.channel
16
+ channel.on_error { |ch, channel_close| EventMachine.stop; raise "channel error: #{channel_close.reply_text}" }
17
+
18
+ # this exchange has no bindings, so messages published to it cannot be routed.
19
+ exchange = channel.fanout("amqpgem.examples.fanout", :auto_delete => true)
20
+ exchange.on_return do |basic_return, metadata, payload|
21
+ puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
22
+ end
23
+
24
+ EventMachine.add_timer(0.3) {
25
+ 10.times do |i|
26
+ exchange.publish("Message ##{i}", :mandatory => true)
27
+ end
28
+ }
29
+
30
+ EventMachine.add_timer(2) {
31
+ connection.close { EventMachine.stop }
32
+ }
33
+ end
@@ -15,27 +15,28 @@ EventMachine.run do
15
15
 
16
16
  # Subscribers.
17
17
  channel.queue("", :exclusive => true) do |queue|
18
- queue.bind(exchange, :routing_key => "americas.north.#").subscribe do |headers, payload|
19
- puts "An update for North America: #{payload}, routing key is #{headers.routing_key}"
18
+ queue.bind(exchange, :routing_key => "americas.north.#").subscribe do |metadata, payload|
19
+ puts "An update for North America: #{payload}, routing key is #{metadata.routing_key}"
20
20
  end
21
21
  end
22
- channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |headers, payload|
23
- puts "An update for South America: #{payload}, routing key is #{headers.routing_key}"
22
+ channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |metadata, payload|
23
+ puts "An update for South America: #{payload}, routing key is #{metadata.routing_key}"
24
24
  end
25
- channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |headers, payload|
26
- puts "An update for US/California: #{payload}, routing key is #{headers.routing_key}"
25
+ channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |metadata, payload|
26
+ puts "An update for US/California: #{payload}, routing key is #{metadata.routing_key}"
27
27
  end
28
- channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |headers, payload|
29
- puts "An update for Austin, TX: #{payload}, routing key is #{headers.routing_key}"
28
+ channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |metadata, payload|
29
+ puts "An update for Austin, TX: #{payload}, routing key is #{metadata.routing_key}"
30
30
  end
31
- channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |headers, payload|
32
- puts "An update for Rome, Italy: #{payload}, routing key is #{headers.routing_key}"
31
+ channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |metadata, payload|
32
+ puts "An update for Rome, Italy: #{payload}, routing key is #{metadata.routing_key}"
33
33
  end
34
- channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |headers, payload|
35
- puts "An update for Hong Kong: #{payload}, routing key is #{headers.routing_key}"
34
+ channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |metadata, payload|
35
+ puts "An update for Hong Kong: #{payload}, routing key is #{metadata.routing_key}"
36
36
  end
37
37
 
38
- EM.add_timer(1) do
38
+ # publish a bunch of messages after 1 second, when all queues are declared and bound
39
+ EventMachine.add_timer(1) do
39
40
  exchange.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego").
40
41
  publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley").
41
42
  publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco").
@@ -49,13 +50,7 @@ EventMachine.run do
49
50
  end
50
51
 
51
52
 
52
- show_stopper = Proc.new {
53
- connection.close do
54
- EM.stop
55
- end
56
- }
57
-
58
- Signal.trap "INT", show_stopper
53
+ show_stopper = Proc.new { connection.close { EventMachine.stop } }
59
54
  Signal.trap "TERM", show_stopper
60
55
 
61
56
  EM.add_timer(3, show_stopper)
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
+
9
+ examples_dir = File.join(File.dirname(File.expand_path(__FILE__)), "..")
10
+
11
+ certificate_chain_file_path = File.join(examples_dir, "tls_certificates", "client", "cert.pem")
12
+ client_private_key_file_path = File.join(examples_dir, "tls_certificates", "client", "key.pem")
13
+
14
+
15
+ require 'amqp'
16
+
17
+ # This example assumes you have configured RabbitMQ to listen on port 5671
18
+ # for TLS connections (using RabbitMQ configuration file), for example:
19
+ #
20
+ # [
21
+ # {rabbit, [
22
+ # {ssl_listeners, [5671]},
23
+ # {ssl_options, [{cacertfile, "/usr/local/etc/rabbitmq/tls/testca/cacert.pem"},
24
+ # {certfile, "/usr/local/etc/rabbitmq/tls/server/cert.pem"},
25
+ # {keyfile, "/usr/local/etc/rabbitmq/tls/server/key.pem"},
26
+ # {verify, verify_peer},
27
+ # {fail_if_no_peer_cert, true}]}
28
+ # ]}
29
+ # ].
30
+ #
31
+ # See TLS certificates under ./examples/tls_certificates
32
+
33
+ AMQP.start(:port => 5671,
34
+ :ssl => {
35
+ :cert_chain_file => certificate_chain_file_path,
36
+ :private_key_file => client_private_key_file_path
37
+ }) do |connection|
38
+ puts "Connected, authenticated. TLS seems to work."
39
+
40
+ connection.disconnect { puts "Now closing the connection…"; EventMachine.stop }
41
+ end
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+
3
+ module AMQP
4
+ # Very minimalistic, pure Ruby implementation of bit set. Inspired by java.util.BitSet,
5
+ # although significantly smaller in scope.
6
+ class BitSet
7
+
8
+ #
9
+ # API
10
+ #
11
+
12
+ ADDRESS_BITS_PER_WORD = 6
13
+ BITS_PER_WORD = (1 << ADDRESS_BITS_PER_WORD)
14
+ WORD_MASK = 0xffffffffffffffff
15
+
16
+ # @param [Integer] Number of bits in the set
17
+ # @api public
18
+ def initialize(nbits)
19
+ @nbits = nbits
20
+
21
+ self.init_words(nbits)
22
+ end # initialize(nbits)
23
+
24
+ # Sets (flags) given bit. This method allows bits to be set more than once in a row, no exception will be raised.
25
+ #
26
+ # @param [Integer] A bit to set
27
+ # @api public
28
+ def set(i)
29
+ w = self.word_index(i)
30
+ @words[w] |= (1 << i)
31
+ end # set(i)
32
+
33
+ # Fetches flag value for given bit.
34
+ #
35
+ # @param [Integer] A bit to fetch
36
+ # @return [Boolean] true if given bit is set, false otherwise
37
+ # @api public
38
+ def get(i)
39
+ w = self.word_index(i)
40
+
41
+ (@words[w] & (1 << i)) != 0
42
+ end # get(i)
43
+ alias [] get
44
+
45
+ # Unsets (unflags) given bit. This method allows bits to be unset more than once in a row, no exception will be raised.
46
+ #
47
+ # @param [Integer] A bit to unset
48
+ # @api public
49
+ def unset(i)
50
+ w = self.word_index(i)
51
+ return if w.nil?
52
+
53
+ @words[w] &= ~(1 << i)
54
+ end # unset(i)
55
+
56
+ # Clears all bits in the set
57
+ # @api public
58
+ def clear
59
+ self.init_words(@nbits)
60
+ end # clear
61
+
62
+
63
+ #
64
+ # Implementation
65
+ #
66
+
67
+ protected
68
+
69
+ # @private
70
+ def init_words(nbits)
71
+ n = word_index(nbits-1) + 1
72
+ @words = Array.new(n) { 1 }
73
+ end # init_words
74
+
75
+ # @private
76
+ def word_index(i)
77
+ i >> ADDRESS_BITS_PER_WORD
78
+ end # word_index(i)
79
+ end # BitSet
80
+ end # AMQP
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+
3
+ module AMQP
4
+ # A utility class that makes inspection of broker capabilities easier.
5
+ class Broker
6
+
7
+ #
8
+ # API
9
+ #
10
+
11
+ RABBITMQ_PRODUCT = "RabbitMQ".freeze
12
+
13
+ # Broker information
14
+ # @return [Hash]
15
+ # @see Session#server_properties
16
+ attr_reader :properties
17
+
18
+ # @return [Hash] properties Broker information
19
+ # @see Session#server_properties
20
+ def initialize(properties)
21
+ @properties = properties
22
+ end # initialize(properties)
23
+
24
+ # @group Product information
25
+
26
+ # @return [Boolean] true if broker is RabbitMQ
27
+ def rabbitmq?
28
+ self.product == RABBITMQ_PRODUCT
29
+ end # rabbitmq?
30
+
31
+ # @return [String] Broker product information
32
+ def product
33
+ @product ||= @properties["product"]
34
+ end # product
35
+
36
+ # @return [String] Broker version
37
+ def version
38
+ @version ||= @properties["version"]
39
+ end # version
40
+
41
+ # @endgroup
42
+
43
+
44
+
45
+ # @group Product capabilities
46
+
47
+ # @return [Boolean]
48
+ def supports_publisher_confirmations?
49
+ @properties["capabilities"]["publisher_confirms"]
50
+ end # supports_publisher_confirmations?
51
+
52
+ # @return [Boolean]
53
+ def supports_basic_nack?
54
+ @properties["capabilities"]["basic.nack"]
55
+ end # supports_basic_nack?
56
+
57
+ # @return [Boolean]
58
+ def supports_consumer_cancel_notifications?
59
+ @properties["capabilities"]["consumer_cancel_notify"]
60
+ end # supports_consumer_cancel_notifications?
61
+
62
+ # @return [Boolean]
63
+ def supports_exchange_to_exchange_bindings?
64
+ @properties["capabilities"]["exchange_exchange_bindings"]
65
+ end # supports_exchange_to_exchange_bindings?
66
+
67
+
68
+ # @endgroup
69
+
70
+
71
+ end # Broker
72
+ end # AMQP
data/lib/amqp/channel.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require "amqp/int_allocator"
3
4
  require "amqp/exchange"
4
5
  require "amqp/queue"
5
6
 
@@ -156,9 +157,6 @@ module AMQP
156
157
  attr_reader :status
157
158
 
158
159
 
159
- # @note We encourage you to not rely on default AMQP connection and pass connection parameter
160
- # explicitly.
161
- #
162
160
  # @param [AMQP::Session] connection Connection to open this channel on. If not given, default AMQP
163
161
  # connection (accessible via {AMQP.connection}) will be used.
164
162
  # @param [Integer] id Channel id. Must not be greater than max channel id client and broker
@@ -194,6 +192,7 @@ module AMQP
194
192
  #
195
193
  #
196
194
  # @option options [Boolean] :prefetch (nil) Specifies number of messages to prefetch. Channel-specific. See {AMQP::Channel#prefetch}.
195
+ # @option options [Boolean] :auto_recovery (nil) Turns on automatic network failure recovery mode for this channel.
197
196
  #
198
197
  # @yield [channel, open_ok] Yields open channel instance and AMQP method (channel.open-ok) instance. The latter is optional.
199
198
  # @yieldparam [Channel] channel Channel that is successfully open
@@ -205,14 +204,18 @@ module AMQP
205
204
  def initialize(connection = nil, id = self.class.next_channel_id, options = {}, &block)
206
205
  raise 'AMQP can only be used from within EM.run {}' unless EM.reactor_running?
207
206
 
208
- @options = options
209
207
  @connection = connection || AMQP.connection || AMQP.start
208
+ # this means 2nd argument is options
209
+ if id.kind_of?(Hash)
210
+ options = options.merge(id)
211
+ id = self.class.next_channel_id
212
+ end
210
213
 
211
- super(@connection, id)
214
+ super(@connection, id, options)
212
215
 
213
216
  @rpcs = Hash.new
214
217
  # we need this deferrable to mimic what AMQP gem 0.7 does to enable
215
- # the following (HIGHLY discouraged) style of programming some people use in their
218
+ # the following (pseudo-synchronous) style of programming some people use in their
216
219
  # existing codebases:
217
220
  #
218
221
  # connection = AMQP.connect
@@ -242,6 +245,32 @@ module AMQP
242
245
  end # @connection.on_open
243
246
  end
244
247
 
248
+ # @return [Boolean] true if this channel is in automatic recovery mode
249
+ # @see #auto_recovering?
250
+ attr_accessor :auto_recovery
251
+
252
+ # @return [Boolean] true if this channel uses automatic recovery mode
253
+ def auto_recovering?
254
+ @auto_recovery
255
+ end # auto_recovering?
256
+
257
+ # Called by associated connection object when AMQP connection has been re-established
258
+ # (for example, after a network failure).
259
+ #
260
+ # @api plugin
261
+ def auto_recover
262
+ return unless auto_recovering?
263
+
264
+ self.open do
265
+ @channel_is_open_deferrable.succeed
266
+
267
+ # exchanges must be recovered first because queue recovery includes recovery of bindings. MK.
268
+ @exchanges.each { |name, e| e.auto_recover }
269
+ @queues.each { |name, q| q.auto_recover }
270
+ end
271
+ end # auto_recover
272
+
273
+
245
274
 
246
275
 
247
276
  # @group Declaring exchanges
@@ -753,6 +782,8 @@ module AMQP
753
782
  # @return [Queue]
754
783
  # @api public
755
784
  def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {}, &block)
785
+ 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?
786
+
756
787
  if name && !name.empty? && (queue = find_queue(name))
757
788
  extended_opts = Queue.add_default_options(name, opts, block)
758
789
 
@@ -791,6 +822,13 @@ module AMQP
791
822
  register_queue(queue)
792
823
  end
793
824
 
825
+ # @return [Array<AMQP::Queue>] Queues cache for this channel
826
+ # @api plugin
827
+ # @private
828
+ def queues
829
+ @queues
830
+ end # queues
831
+
794
832
  # @endgroup
795
833
 
796
834
 
@@ -872,7 +910,10 @@ module AMQP
872
910
  #
873
911
  # @api public
874
912
  def close(reply_code = 200, reply_text = DEFAULT_REPLY_TEXT, class_id = 0, method_id = 0, &block)
875
- super(reply_code, reply_text, class_id, method_id, &block)
913
+ r = super(reply_code, reply_text, class_id, method_id, &block)
914
+ self.class.release_channel_id(@id)
915
+
916
+ r
876
917
  end
877
918
 
878
919
  # @endgroup
@@ -1090,8 +1131,10 @@ module AMQP
1090
1131
  #
1091
1132
  # @api plugin
1092
1133
  # @private
1093
- def handle_connection_interruption(exception = nil)
1094
- super(exception)
1134
+ def handle_connection_interruption(reason = nil)
1135
+ super(reason)
1136
+
1137
+ self.class.release_channel_id(@id) unless auto_recovering?
1095
1138
  @channel_is_open_deferrable = AMQ::Client::EventMachineClient::Deferrable.new
1096
1139
  end
1097
1140
 
@@ -1102,18 +1145,55 @@ module AMQP
1102
1145
  @channel_id_mutex ||= Mutex.new
1103
1146
  end
1104
1147
 
1105
- # Returns incrementing channel id. This method is thread safe.
1148
+ # Returns next available channel id. This method is thread safe.
1149
+ #
1106
1150
  # @return [Fixnum]
1107
1151
  # @api public
1152
+ # @see Channel.release_channel_id
1153
+ # @see Channel.reset_channel_id_allocator
1108
1154
  def self.next_channel_id
1109
1155
  channel_id_mutex.synchronize do
1110
- @last_channel_id ||= 0
1111
- @last_channel_id += 1
1156
+ self.initialize_channel_id_allocator
1112
1157
 
1113
- @last_channel_id
1158
+ @int_allocator.allocate
1114
1159
  end
1115
1160
  end
1116
1161
 
1162
+ # Releases previously allocated channel id. This method is thread safe.
1163
+ #
1164
+ # @param [Fixnum] Channel id to release
1165
+ # @api public
1166
+ # @see Channel.next_channel_id
1167
+ # @see Channel.reset_channel_id_allocator
1168
+ def self.release_channel_id(i)
1169
+ channel_id_mutex.synchronize do
1170
+ self.initialize_channel_id_allocator
1171
+
1172
+ @int_allocator.release(i)
1173
+ end
1174
+ end # self.release_channel_id(i)
1175
+
1176
+ # Resets channel allocator. This method is thread safe.
1177
+ # @api public
1178
+ # @see Channel.next_channel_id
1179
+ # @see Channel.release_channel_id
1180
+ def self.reset_channel_id_allocator
1181
+ channel_id_mutex.synchronize do
1182
+ initialize_channel_id_allocator
1183
+
1184
+ @int_allocator.reset
1185
+ end
1186
+ end # self.reset_channel_id_allocator
1187
+
1188
+
1189
+ # @private
1190
+ def self.initialize_channel_id_allocator
1191
+ # TODO: ideally, this should be in agreement with agreed max number of channels of the connection,
1192
+ # but it is possible that value either not yet available. MK.
1193
+ max_channel = (1 << 16) - 1
1194
+ @int_allocator ||= IntAllocator.new(1, max_channel)
1195
+ end # self.initialize_channel_id_allocator
1196
+
1117
1197
  # @private
1118
1198
  # @api plugin
1119
1199
  def register_rpc(rpc)