bunny 1.3.0 → 2.17.0

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.
Files changed (143) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +7 -1
  4. data/.rspec +1 -3
  5. data/.travis.yml +21 -14
  6. data/CONTRIBUTING.md +132 -0
  7. data/ChangeLog.md +887 -1
  8. data/Gemfile +13 -13
  9. data/LICENSE +1 -1
  10. data/README.md +46 -60
  11. data/Rakefile +54 -0
  12. data/bunny.gemspec +5 -11
  13. data/docker-compose.yml +28 -0
  14. data/docker/Dockerfile +24 -0
  15. data/docker/apt/preferences.d/erlang +3 -0
  16. data/docker/apt/sources.list.d/bintray.rabbitmq.list +2 -0
  17. data/docker/docker-entrypoint.sh +26 -0
  18. data/docker/rabbitmq.conf +29 -0
  19. data/examples/connection/automatic_recovery_with_basic_get.rb +1 -1
  20. data/examples/connection/automatic_recovery_with_client_named_queues.rb +1 -1
  21. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +1 -1
  22. data/examples/connection/automatic_recovery_with_republishing.rb +1 -1
  23. data/examples/connection/automatic_recovery_with_server_named_queues.rb +1 -1
  24. data/examples/connection/channel_level_exception.rb +1 -9
  25. data/examples/connection/disabled_automatic_recovery.rb +1 -1
  26. data/examples/connection/heartbeat.rb +1 -1
  27. data/examples/consumers/high_and_low_priority.rb +1 -1
  28. data/examples/guides/extensions/alternate_exchange.rb +2 -0
  29. data/examples/guides/extensions/basic_nack.rb +1 -1
  30. data/examples/guides/extensions/dead_letter_exchange.rb +1 -1
  31. data/examples/guides/getting_started/hello_world.rb +2 -0
  32. data/examples/guides/getting_started/weathr.rb +2 -0
  33. data/examples/guides/queues/one_off_consumer.rb +2 -0
  34. data/examples/guides/queues/redeliveries.rb +4 -2
  35. data/lib/bunny.rb +8 -4
  36. data/lib/bunny/channel.rb +268 -153
  37. data/lib/bunny/channel_id_allocator.rb +6 -4
  38. data/lib/bunny/concurrent/continuation_queue.rb +34 -13
  39. data/lib/bunny/consumer_work_pool.rb +34 -6
  40. data/lib/bunny/cruby/socket.rb +48 -21
  41. data/lib/bunny/cruby/ssl_socket.rb +65 -4
  42. data/lib/bunny/exceptions.rb +25 -4
  43. data/lib/bunny/exchange.rb +24 -28
  44. data/lib/bunny/get_response.rb +1 -1
  45. data/lib/bunny/heartbeat_sender.rb +3 -2
  46. data/lib/bunny/jruby/socket.rb +23 -6
  47. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  48. data/lib/bunny/queue.rb +31 -22
  49. data/lib/bunny/reader_loop.rb +31 -18
  50. data/lib/bunny/session.rb +448 -159
  51. data/lib/bunny/test_kit.rb +14 -0
  52. data/lib/bunny/timeout.rb +1 -12
  53. data/lib/bunny/transport.rb +205 -98
  54. data/lib/bunny/version.rb +1 -1
  55. data/repl +1 -1
  56. data/spec/config/enabled_plugins +1 -0
  57. data/spec/config/rabbitmq.conf +13 -0
  58. data/spec/higher_level_api/integration/basic_ack_spec.rb +175 -16
  59. data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
  60. data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
  61. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
  62. data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
  63. data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
  64. data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
  65. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
  66. data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
  67. data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
  68. data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
  69. data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
  70. data/spec/higher_level_api/integration/connection_recovery_spec.rb +424 -221
  71. data/spec/higher_level_api/integration/connection_spec.rb +300 -126
  72. data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
  73. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
  74. data/spec/higher_level_api/integration/dead_lettering_spec.rb +34 -11
  75. data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
  76. data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
  77. data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
  78. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
  79. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
  80. data/spec/higher_level_api/integration/heartbeat_spec.rb +26 -8
  81. data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
  82. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
  83. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +156 -42
  84. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
  85. data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
  86. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
  87. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  88. data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
  89. data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
  90. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
  91. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
  92. data/spec/higher_level_api/integration/tls_connection_spec.rb +224 -89
  93. data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
  94. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  95. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  96. data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
  97. data/spec/issues/issue100_spec.rb +11 -11
  98. data/spec/issues/issue141_spec.rb +13 -14
  99. data/spec/issues/issue202_spec.rb +1 -1
  100. data/spec/issues/issue224_spec.rb +40 -0
  101. data/spec/issues/issue465_spec.rb +32 -0
  102. data/spec/issues/issue549_spec.rb +30 -0
  103. data/spec/issues/issue78_spec.rb +21 -24
  104. data/spec/issues/issue83_spec.rb +5 -6
  105. data/spec/issues/issue97_spec.rb +44 -45
  106. data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
  107. data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
  108. data/spec/spec_helper.rb +8 -26
  109. data/spec/stress/channel_close_stress_spec.rb +64 -0
  110. data/spec/stress/channel_open_stress_spec.rb +15 -9
  111. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
  112. data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
  113. data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
  114. data/spec/stress/connection_open_close_spec.rb +9 -9
  115. data/spec/stress/merry_go_round_spec.rb +105 -0
  116. data/spec/tls/client_key.pem +49 -25
  117. data/spec/tls/generate-server-cert.sh +8 -0
  118. data/spec/tls/server-openssl.cnf +10 -0
  119. data/spec/tls/server.csr +16 -0
  120. data/spec/tls/server_key.pem +49 -25
  121. data/spec/toxiproxy_helper.rb +28 -0
  122. data/spec/unit/bunny_spec.rb +5 -5
  123. data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
  124. data/spec/unit/concurrent/condition_spec.rb +8 -8
  125. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
  126. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
  127. data/spec/unit/exchange_recovery_spec.rb +39 -0
  128. data/spec/unit/version_delivery_tag_spec.rb +3 -3
  129. metadata +65 -47
  130. data/.ruby-version +0 -1
  131. data/lib/bunny/compatibility.rb +0 -24
  132. data/lib/bunny/system_timer.rb +0 -20
  133. data/spec/compatibility/queue_declare_spec.rb +0 -44
  134. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
  135. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  136. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  137. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  138. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  139. data/spec/stress/long_running_consumer_spec.rb +0 -83
  140. data/spec/tls/cacert.pem +0 -18
  141. data/spec/tls/client_cert.pem +0 -18
  142. data/spec/tls/server_cert.pem +0 -18
  143. data/spec/unit/system_timer_spec.rb +0 -10
@@ -8,7 +8,7 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
8
 
9
9
  require 'bunny'
10
10
 
11
- conn = Bunny.new(:heartbeat_interval => 8)
11
+ conn = Bunny.new(:heartbeat_timeout => 8)
12
12
  conn.start
13
13
 
14
14
  ch = conn.create_channel
@@ -8,7 +8,7 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
8
 
9
9
  require 'bunny'
10
10
 
11
- conn = Bunny.new(:heartbeat_interval => 8)
11
+ conn = Bunny.new(heartbeat_timeout: 8)
12
12
  conn.start
13
13
 
14
14
  ch = conn.create_channel
@@ -8,7 +8,7 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
8
 
9
9
  require 'bunny'
10
10
 
11
- conn = Bunny.new(:heartbeat_interval => 8)
11
+ conn = Bunny.new(heartbeat_timeout: 8)
12
12
  conn.start
13
13
 
14
14
  ch1 = conn.create_channel
@@ -8,7 +8,7 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
8
 
9
9
  require 'Bunny'
10
10
 
11
- conn = Bunny.new(:heartbeat_interval => 8)
11
+ conn = Bunny.new(:heartbeat_timeout => 8)
12
12
  conn.start
13
13
 
14
14
  ch0 = conn.create_channel
@@ -8,7 +8,7 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
8
 
9
9
  require 'bunny'
10
10
 
11
- conn = Bunny.new(:heartbeat_interval => 8)
11
+ conn = Bunny.new(heartbeat_timeout: 8)
12
12
  conn.start
13
13
 
14
14
  ch = conn.create_channel
@@ -8,17 +8,9 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
8
 
9
9
  require 'bunny'
10
10
 
11
- conn = Bunny.new(:heartbeat_interval => 8)
11
+ conn = Bunny.new(heartbeat_timeout: 8)
12
12
  conn.start
13
13
 
14
- begin
15
- ch1 = conn.create_channel
16
- ch1.queue_delete("queue_that_should_not_exist#{rand}")
17
- rescue Bunny::NotFound => e
18
- puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}"
19
- end
20
-
21
-
22
14
  begin
23
15
  ch2 = conn.create_channel
24
16
  q = "bunny.examples.recovery.q#{rand}"
@@ -8,7 +8,7 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
8
8
 
9
9
  require 'bunny'
10
10
 
11
- conn = Bunny.new(:heartbeat_interval => 8, :automatically_recover => false)
11
+ conn = Bunny.new(heartbeat_timeout: 8, automatically_recover: false)
12
12
  conn.start
13
13
 
14
14
  ch = conn.create_channel
@@ -9,7 +9,7 @@ $:.unshift(File.expand_path("../../../lib", __FILE__))
9
9
  require 'bunny'
10
10
 
11
11
 
12
- conn = Bunny.new(:heartbeat_interval => 2)
12
+ conn = Bunny.new(:heartbeat_timeout => 2)
13
13
  conn.start
14
14
 
15
15
  c = conn.create_channel
@@ -11,7 +11,7 @@ require 'bunny'
11
11
  HIGH_PRIORITY_Q = "bunny.examples.priority.hilo.high"
12
12
  LOW_PRIORITY_Q = "bunny.examples.priority.hilo.low"
13
13
 
14
- conn = Bunny.new(:heartbeat_interval => 8)
14
+ conn = Bunny.new(heartbeat_timeout: 8)
15
15
  conn.start
16
16
 
17
17
  ch1 = conn.create_channel
@@ -4,6 +4,8 @@
4
4
  require "rubygems"
5
5
  require "bunny"
6
6
 
7
+ STDOUT.sync = true
8
+
7
9
  puts "=> Demonstrating alternate exchanges"
8
10
  puts
9
11
 
@@ -18,7 +18,7 @@ q = ch.queue("", :exclusive => true)
18
18
  end
19
19
 
20
20
  20.times do
21
- delivery_info, _, _ = q.pop(:ack => true)
21
+ delivery_info, _, _ = q.pop(:manual_ack => true)
22
22
 
23
23
  if delivery_info.delivery_tag == 20
24
24
  # requeue them all at once with basic.nack
@@ -20,7 +20,7 @@ dlq = ch.queue("", :exclusive => true).bind(dlx)
20
20
  x.publish("")
21
21
  sleep 0.2
22
22
 
23
- delivery_info, _, _ = q.pop(:ack => true)
23
+ delivery_info, _, _ = q.pop(:manual_ack => true)
24
24
  puts "#{dlq.message_count} messages dead lettered so far"
25
25
  puts "Rejecting a message"
26
26
  ch.nack(delivery_info.delivery_tag)
@@ -4,6 +4,8 @@
4
4
  require "rubygems"
5
5
  require "bunny"
6
6
 
7
+ STDOUT.sync = true
8
+
7
9
  conn = Bunny.new
8
10
  conn.start
9
11
 
@@ -4,6 +4,8 @@
4
4
  require "rubygems"
5
5
  require "bunny"
6
6
 
7
+ STDOUT.sync = true
8
+
7
9
  connection = Bunny.new
8
10
  connection.start
9
11
 
@@ -4,6 +4,8 @@
4
4
  require "rubygems"
5
5
  require "bunny"
6
6
 
7
+ STDOUT.sync = true
8
+
7
9
  conn = Bunny.new
8
10
  conn.start
9
11
 
@@ -4,6 +4,8 @@
4
4
  require "rubygems"
5
5
  require "bunny"
6
6
 
7
+ STDOUT.sync = true
8
+
7
9
  puts "=> Subscribing for messages using explicit acknowledgements model"
8
10
  puts
9
11
 
@@ -29,7 +31,7 @@ x = ch3.direct("amq.direct")
29
31
  q1 = ch1.queue("bunny.examples.acknowledgements.explicit", :auto_delete => false)
30
32
  q1.purge
31
33
 
32
- q1.bind(x).subscribe(:ack => true, :block => false) do |delivery_info, properties, payload|
34
+ q1.bind(x).subscribe(:manual_ack => true, :block => false) do |delivery_info, properties, payload|
33
35
  # do some work
34
36
  sleep(0.2)
35
37
 
@@ -46,7 +48,7 @@ q1.bind(x).subscribe(:ack => true, :block => false) do |delivery_info, propertie
46
48
  end
47
49
 
48
50
  q2 = ch2.queue("bunny.examples.acknowledgements.explicit", :auto_delete => false)
49
- q2.bind(x).subscribe(:ack => true, :block => false) do |delivery_info, properties, payload|
51
+ q2.bind(x).subscribe(:manual_ack => true, :block => false) do |delivery_info, properties, payload|
50
52
  # do some work
51
53
  sleep(0.2)
52
54
 
data/lib/bunny.rb CHANGED
@@ -17,7 +17,7 @@ begin
17
17
  require "openssl"
18
18
 
19
19
  require "bunny/ssl_socket"
20
- rescue LoadError => e
20
+ rescue LoadError
21
21
  # no-op
22
22
  end
23
23
 
@@ -50,7 +50,7 @@ module Bunny
50
50
  AMQ::Protocol::PROTOCOL_VERSION
51
51
  end
52
52
 
53
- # Instantiates a new connection. The actual connection network
53
+ # Instantiates a new connection. The actual network
54
54
  # connection is started with {Bunny::Session#start}
55
55
  #
56
56
  # @return [Bunny::Session]
@@ -58,7 +58,7 @@ module Bunny
58
58
  # @see http://rubybunny.info/articles/getting_started.html
59
59
  # @see http://rubybunny.info/articles/connecting.html
60
60
  # @api public
61
- def self.new(connection_string_or_opts = {}, opts = {}, &block)
61
+ def self.new(connection_string_or_opts = ENV['RABBITMQ_URL'], opts = {})
62
62
  if connection_string_or_opts.respond_to?(:keys) && opts.empty?
63
63
  opts = connection_string_or_opts
64
64
  end
@@ -70,9 +70,13 @@ module Bunny
70
70
  end
71
71
 
72
72
 
73
- def self.run(connection_string_or_opts = {}, opts = {}, &block)
73
+ def self.run(connection_string_or_opts = ENV['RABBITMQ_URL'], opts = {}, &block)
74
74
  raise ArgumentError, 'Bunny#run requires a block' unless block
75
75
 
76
+ if connection_string_or_opts.respond_to?(:keys) && opts.empty?
77
+ opts = connection_string_or_opts
78
+ end
79
+
76
80
  client = Session.new(connection_string_or_opts, opts)
77
81
 
78
82
  begin
data/lib/bunny/channel.rb CHANGED
@@ -37,11 +37,10 @@ module Bunny
37
37
  # Channels can be opened either via `Bunny::Session#create_channel` (sufficient in the majority
38
38
  # of cases) or by instantiating `Bunny::Channel` directly:
39
39
  #
40
- # @example Using {Bunny::Session#create_channel}:
41
- # conn = Bunny.new
42
- # conn.start
40
+ # conn = Bunny.new
41
+ # conn.start
43
42
  #
44
- # ch = conn.create_channel
43
+ # ch = conn.create_channel
45
44
  #
46
45
  # This will automatically allocate a channel id.
47
46
  #
@@ -51,10 +50,8 @@ module Bunny
51
50
  # closed, too. Closed channels can no longer be used. Attempts to use them will raise
52
51
  # {Bunny::ChannelAlreadyClosed}.
53
52
  #
54
- # @example
55
- #
56
- # ch = conn.create_channel
57
- # ch.close
53
+ # ch = conn.create_channel
54
+ # ch.close
58
55
  #
59
56
  # ## Higher-level API
60
57
  #
@@ -159,6 +156,8 @@ module Bunny
159
156
 
160
157
  # @return [Integer] active basic.qos prefetch value
161
158
  attr_reader :prefetch_count
159
+ # @return [Integer] active basic.qos prefetch global mode
160
+ attr_reader :prefetch_global
162
161
 
163
162
  DEFAULT_CONTENT_TYPE = "application/octet-stream".freeze
164
163
  SHORTSTR_LIMIT = 255
@@ -170,6 +169,17 @@ module Bunny
170
169
  @connection = connection
171
170
  @logger = connection.logger
172
171
  @id = id || @connection.next_channel_id
172
+
173
+ # channel allocator is exhausted
174
+ if @id < 0
175
+ msg = "Cannot open a channel: max number of channels on connection reached. Connection channel_max value: #{@connection.channel_max}"
176
+ @logger.error(msg)
177
+
178
+ raise msg
179
+ else
180
+ @logger.debug { "Allocated channel id: #{@id}" }
181
+ end
182
+
173
183
  @status = :opening
174
184
 
175
185
  @connection.register_channel(self)
@@ -183,6 +193,9 @@ module Bunny
183
193
  @publishing_mutex = @connection.mutex_impl.new
184
194
  @consumer_mutex = @connection.mutex_impl.new
185
195
 
196
+ @queue_mutex = @connection.mutex_impl.new
197
+ @exchange_mutex = @connection.mutex_impl.new
198
+
186
199
  @unconfirmed_set_mutex = @connection.mutex_impl.new
187
200
 
188
201
  self.reset_continuations
@@ -195,18 +208,19 @@ module Bunny
195
208
  @threads_waiting_on_basic_get_continuations = Set.new
196
209
 
197
210
  @next_publish_seq_no = 0
211
+ @delivery_tag_offset = 0
198
212
 
199
213
  @recoveries_counter = Bunny::Concurrent::AtomicFixnum.new(0)
200
214
  @uncaught_exception_handler = Proc.new do |e, consumer|
201
- @logger.error "Uncaught exception from consumer #{consumer.to_s}: #{e.message}"
215
+ @logger.error "Uncaught exception from consumer #{consumer.to_s}: #{e.inspect} @ #{e.backtrace[0]}"
202
216
  end
203
217
  end
204
218
 
205
219
  attr_reader :recoveries_counter
206
220
 
207
221
  # @private
208
- def read_write_timeout
209
- @connection.read_write_timeout
222
+ def wait_on_continuations_timeout
223
+ @connection.transport_write_timeout
210
224
  end
211
225
 
212
226
  # Opens the channel and resets its internal state
@@ -230,8 +244,12 @@ module Bunny
230
244
  # {Bunny::Queue}, {Bunny::Exchange} and {Bunny::Consumer} instances.
231
245
  # @api public
232
246
  def close
247
+ # see bunny#528
248
+ raise_if_no_longer_open!
249
+
233
250
  @connection.close_channel(self)
234
- closed!
251
+ @status = :closed
252
+ @work_pool.shutdown
235
253
  maybe_kill_consumer_work_pool!
236
254
  end
237
255
 
@@ -247,7 +265,6 @@ module Bunny
247
265
  @status == :closed
248
266
  end
249
267
 
250
-
251
268
  #
252
269
  # @group Backwards compatibility with 0.8.0
253
270
  #
@@ -296,7 +313,7 @@ module Bunny
296
313
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
297
314
  # @api public
298
315
  def fanout(name, opts = {})
299
- Exchange.new(self, :fanout, name, opts)
316
+ find_exchange(name) || Exchange.new(self, :fanout, name, opts)
300
317
  end
301
318
 
302
319
  # Declares a direct exchange or looks it up in the cache of previously
@@ -314,7 +331,7 @@ module Bunny
314
331
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
315
332
  # @api public
316
333
  def direct(name, opts = {})
317
- Exchange.new(self, :direct, name, opts)
334
+ find_exchange(name) || Exchange.new(self, :direct, name, opts)
318
335
  end
319
336
 
320
337
  # Declares a topic exchange or looks it up in the cache of previously
@@ -332,7 +349,7 @@ module Bunny
332
349
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
333
350
  # @api public
334
351
  def topic(name, opts = {})
335
- Exchange.new(self, :topic, name, opts)
352
+ find_exchange(name) || Exchange.new(self, :topic, name, opts)
336
353
  end
337
354
 
338
355
  # Declares a headers exchange or looks it up in the cache of previously
@@ -350,14 +367,14 @@ module Bunny
350
367
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
351
368
  # @api public
352
369
  def headers(name, opts = {})
353
- Exchange.new(self, :headers, name, opts)
370
+ find_exchange(name) || Exchange.new(self, :headers, name, opts)
354
371
  end
355
372
 
356
373
  # Provides access to the default exchange
357
374
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
358
375
  # @api public
359
376
  def default_exchange
360
- self.direct(AMQ::Protocol::EMPTY_STRING, :no_declare => true)
377
+ Exchange.default(self)
361
378
  end
362
379
 
363
380
  # Declares a headers exchange or looks it up in the cache of previously
@@ -398,27 +415,28 @@ module Bunny
398
415
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
399
416
  # @api public
400
417
  def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {})
418
+ throw ArgumentError.new("queue name must not be nil") if name.nil?
419
+
401
420
  q = find_queue(name) || Bunny::Queue.new(self, name, opts)
402
421
 
403
422
  register_queue(q)
404
423
  end
405
424
 
425
+ # Declares a new server-named queue that is automatically deleted when the
426
+ # connection is closed.
427
+ #
428
+ # @return [Bunny::Queue] Queue that was declared
429
+ # @see #queue
430
+ # @api public
431
+ def temporary_queue(opts = {})
432
+ queue("", opts.merge(:exclusive => true))
433
+ end
434
+
406
435
  # @endgroup
407
436
 
408
437
 
409
438
  # @group QoS and Flow Control
410
439
 
411
- # Sets how many messages will be given to consumers on this channel before they
412
- # have to acknowledge or reject one of the previously consumed messages
413
- #
414
- # @param [Integer] prefetch_count Prefetch (QoS setting) for this channel
415
- # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
416
- # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
417
- # @api public
418
- def prefetch(count)
419
- self.basic_qos(count, false)
420
- end
421
-
422
440
  # Flow control. When set to false, RabbitMQ will stop delivering messages on this
423
441
  # channel.
424
442
  #
@@ -451,9 +469,7 @@ module Bunny
451
469
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
452
470
  # @api public
453
471
  def reject(delivery_tag, requeue = false)
454
- guarding_against_stale_delivery_tags(delivery_tag) do
455
- basic_reject(delivery_tag.to_i, requeue)
456
- end
472
+ basic_reject(delivery_tag.to_i, requeue)
457
473
  end
458
474
 
459
475
  # Acknowledges a message. Acknowledged messages are completely removed from the queue.
@@ -464,9 +480,7 @@ module Bunny
464
480
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
465
481
  # @api public
466
482
  def ack(delivery_tag, multiple = false)
467
- guarding_against_stale_delivery_tags(delivery_tag) do
468
- basic_ack(delivery_tag.to_i, multiple)
469
- end
483
+ basic_ack(delivery_tag.to_i, multiple)
470
484
  end
471
485
  alias acknowledge ack
472
486
 
@@ -481,9 +495,7 @@ module Bunny
481
495
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
482
496
  # @api public
483
497
  def nack(delivery_tag, multiple = false, requeue = false)
484
- guarding_against_stale_delivery_tags(delivery_tag) do
485
- basic_nack(delivery_tag.to_i, multiple, requeue)
486
- end
498
+ basic_nack(delivery_tag.to_i, multiple, requeue)
487
499
  end
488
500
 
489
501
  # @endgroup
@@ -553,7 +565,7 @@ module Bunny
553
565
  opts[:mandatory],
554
566
  false,
555
567
  @connection.frame_max)
556
- @connection.send_frameset_without_timeout(frames, self)
568
+ @connection.send_frameset(frames, self)
557
569
 
558
570
  self
559
571
  end
@@ -565,7 +577,8 @@ module Bunny
565
577
  # @param [String] queue Queue name
566
578
  # @param [Hash] opts Options
567
579
  #
568
- # @option opts [Boolean] :ack (true) Will this message be acknowledged manually?
580
+ # @option opts [Boolean] :ack (true) [DEPRECATED] Use :manual_ack instead
581
+ # @option opts [Boolean] :manual_ack (true) Will this message be acknowledged manually?
569
582
  #
570
583
  # @return [Array] A triple of delivery info, message properties and message content
571
584
  #
@@ -574,15 +587,20 @@ module Bunny
574
587
  # conn.start
575
588
  # ch = conn.create_channel
576
589
  # # here we assume the queue already exists and has messages
577
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue1", :ack => true)
590
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue1", :manual_ack => true)
578
591
  # ch.acknowledge(delivery_info.delivery_tag)
579
592
  # @see Bunny::Queue#pop
580
593
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
581
594
  # @api public
582
- def basic_get(queue, opts = {:ack => true})
595
+ def basic_get(queue, opts = {:manual_ack => true})
583
596
  raise_if_no_longer_open!
584
597
 
585
- @connection.send_frame(AMQ::Protocol::Basic::Get.encode(@id, queue, !(opts[:ack])))
598
+ unless opts[:ack].nil?
599
+ warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
600
+ opts[:manual_ack] = opts[:ack]
601
+ end
602
+
603
+ @connection.send_frame(AMQ::Protocol::Basic::Get.encode(@id, queue, !(opts[:manual_ack])))
586
604
  # this is a workaround for the edge case when basic_get is called in a tight loop
587
605
  # and network goes down we need to perform recovery. The problem is, basic_get will
588
606
  # keep blocking the thread that calls it without clear way to constantly unblock it
@@ -590,40 +608,59 @@ module Bunny
590
608
  # implementation (and even more correct and convenient ones, such as wait/notify, should
591
609
  # we implement them). So we return a triple of nils immediately which apps should be
592
610
  # able to handle anyway as "got no message, no need to act". MK.
593
- @last_basic_get_response = if @connection.open?
594
- wait_on_basic_get_continuations
595
- else
596
- [nil, nil, nil]
597
- end
611
+ last_basic_get_response = if @connection.open?
612
+ begin
613
+ wait_on_basic_get_continuations
614
+ rescue Timeout::Error => e
615
+ raise_if_continuation_resulted_in_a_channel_error!
616
+ raise e
617
+ end
618
+ else
619
+ [nil, nil, nil]
620
+ end
598
621
 
599
622
  raise_if_continuation_resulted_in_a_channel_error!
600
- @last_basic_get_response
623
+ last_basic_get_response
601
624
  end
602
625
 
626
+ # prefetch_count is of type short in the protocol. MK.
627
+ MAX_PREFETCH_COUNT = (2 ** 16) - 1
628
+
603
629
  # Controls message delivery rate using basic.qos AMQP 0.9.1 method.
604
630
  #
605
631
  # @param [Integer] prefetch_count How many messages can consumers on this channel be given at a time
606
632
  # (before they have to acknowledge or reject one of the earlier received messages)
607
- # @param [Boolean] global (false) Ignored, as it is not supported by RabbitMQ
633
+ # @param [Boolean] global
634
+ # Whether to use global mode for prefetch:
635
+ # - +false+: per-consumer
636
+ # - +true+: per-channel
637
+ # Note that the default value (+false+) hasn't actually changed, but
638
+ # previous documentation described that as meaning per-channel and
639
+ # unsupported in RabbitMQ, whereas it now actually appears to mean
640
+ # per-consumer and supported
641
+ # (https://www.rabbitmq.com/consumer-prefetch.html).
608
642
  # @return [AMQ::Protocol::Basic::QosOk] RabbitMQ response
609
643
  # @see Bunny::Channel#prefetch
610
644
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
611
645
  # @api public
612
646
  def basic_qos(count, global = false)
613
- raise ArgumentError.new("prefetch count must be a positive integer, given: #{prefetch_count}") if count < 0
647
+ raise ArgumentError.new("prefetch count must be a positive integer, given: #{count}") if count < 0
648
+ raise ArgumentError.new("prefetch count must be no greater than #{MAX_PREFETCH_COUNT}, given: #{count}") if count > MAX_PREFETCH_COUNT
614
649
  raise_if_no_longer_open!
615
650
 
616
651
  @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
617
652
 
618
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
653
+ with_continuation_timeout do
619
654
  @last_basic_qos_ok = wait_on_continuations
620
655
  end
621
656
  raise_if_continuation_resulted_in_a_channel_error!
622
657
 
623
- @prefetch_count = count
658
+ @prefetch_count = count
659
+ @prefetch_global = global
624
660
 
625
661
  @last_basic_qos_ok
626
662
  end
663
+ alias prefetch basic_qos
627
664
 
628
665
  # Redeliver unacknowledged messages
629
666
  #
@@ -634,7 +671,7 @@ module Bunny
634
671
  raise_if_no_longer_open!
635
672
 
636
673
  @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
637
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
674
+ with_continuation_timeout do
638
675
  @last_basic_recover_ok = wait_on_continuations
639
676
  end
640
677
  raise_if_continuation_resulted_in_a_channel_error!
@@ -664,7 +701,7 @@ module Bunny
664
701
  #
665
702
  # ch = conn.create_channel
666
703
  # q.subscribe do |delivery_info, properties, payload|
667
- # # requeue the message
704
+ # # reject the message
668
705
  # ch.basic_reject(delivery_info.delivery_tag, false)
669
706
  # end
670
707
  #
@@ -674,17 +711,19 @@ module Bunny
674
711
  #
675
712
  # ch = conn.create_channel
676
713
  # # we assume the queue exists and has messages
677
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
714
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
678
715
  # ch.basic_reject(delivery_info.delivery_tag, true)
679
716
  #
680
717
  # @see Bunny::Channel#basic_nack
681
718
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
682
719
  # @api public
683
- def basic_reject(delivery_tag, requeue)
684
- raise_if_no_longer_open!
685
- @connection.send_frame(AMQ::Protocol::Basic::Reject.encode(@id, delivery_tag, requeue))
720
+ def basic_reject(delivery_tag, requeue = false)
721
+ guarding_against_stale_delivery_tags(delivery_tag) do
722
+ raise_if_no_longer_open!
723
+ @connection.send_frame(AMQ::Protocol::Basic::Reject.encode(@id, delivery_tag, requeue))
686
724
 
687
- nil
725
+ nil
726
+ end
688
727
  end
689
728
 
690
729
  # Acknowledges a delivery (message).
@@ -700,7 +739,7 @@ module Bunny
700
739
  # ch = conn.create_channel
701
740
  # q.subscribe do |delivery_info, properties, payload|
702
741
  # # requeue the message
703
- # ch.basic_ack(delivery_info.delivery_tag)
742
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
704
743
  # end
705
744
  #
706
745
  # @example Ack a message fetched via basic.get
@@ -709,8 +748,8 @@ module Bunny
709
748
  #
710
749
  # ch = conn.create_channel
711
750
  # # we assume the queue exists and has messages
712
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
713
- # ch.basic_ack(delivery_info.delivery_tag)
751
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
752
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
714
753
  #
715
754
  # @example Ack multiple messages fetched via basic.get
716
755
  # conn = Bunny.new
@@ -718,20 +757,21 @@ module Bunny
718
757
  #
719
758
  # ch = conn.create_channel
720
759
  # # we assume the queue exists and has messages
721
- # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :ack => true)
722
- # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :ack => true)
723
- # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :ack => true)
760
+ # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
761
+ # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
762
+ # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
724
763
  # # ack all fetched messages up to payload3
725
- # ch.basic_ack(delivery_info.delivery_tag, true)
764
+ # ch.basic_ack(delivery_info.delivery_tag.to_i, true)
726
765
  #
727
766
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
728
- # @see #basic_ack_known_delivery_tag
729
767
  # @api public
730
- def basic_ack(delivery_tag, multiple)
731
- raise_if_no_longer_open!
732
- @connection.send_frame(AMQ::Protocol::Basic::Ack.encode(@id, delivery_tag, multiple))
768
+ def basic_ack(delivery_tag, multiple = false)
769
+ guarding_against_stale_delivery_tags(delivery_tag) do
770
+ raise_if_no_longer_open!
771
+ @connection.send_frame(AMQ::Protocol::Basic::Ack.encode(@id, delivery_tag, multiple))
733
772
 
734
- nil
773
+ nil
774
+ end
735
775
  end
736
776
 
737
777
  # Rejects or requeues messages just like {Bunny::Channel#basic_reject} but can do so
@@ -768,7 +808,7 @@ module Bunny
768
808
  #
769
809
  # ch = conn.create_channel
770
810
  # # we assume the queue exists and has messages
771
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
811
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
772
812
  # ch.basic_nack(delivery_info.delivery_tag, false, true)
773
813
  #
774
814
  #
@@ -778,9 +818,9 @@ module Bunny
778
818
  #
779
819
  # ch = conn.create_channel
780
820
  # # we assume the queue exists and has messages
781
- # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :ack => true)
782
- # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :ack => true)
783
- # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :ack => true)
821
+ # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
822
+ # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
823
+ # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
784
824
  # # requeue all fetched messages up to payload3
785
825
  # ch.basic_nack(delivery_info.delivery_tag, true, true)
786
826
  #
@@ -788,13 +828,15 @@ module Bunny
788
828
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
789
829
  # @api public
790
830
  def basic_nack(delivery_tag, multiple = false, requeue = false)
791
- raise_if_no_longer_open!
792
- @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
793
- delivery_tag,
794
- multiple,
795
- requeue))
831
+ guarding_against_stale_delivery_tags(delivery_tag) do
832
+ raise_if_no_longer_open!
833
+ @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
834
+ delivery_tag,
835
+ multiple,
836
+ requeue))
796
837
 
797
- nil
838
+ nil
839
+ end
798
840
  end
799
841
 
800
842
  # Registers a consumer for queue. Delivered messages will be handled with the block
@@ -836,7 +878,7 @@ module Bunny
836
878
  arguments))
837
879
 
838
880
  begin
839
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
881
+ with_continuation_timeout do
840
882
  @last_basic_consume_ok = wait_on_continuations
841
883
  end
842
884
  rescue Exception => e
@@ -886,7 +928,7 @@ module Bunny
886
928
  consumer.arguments))
887
929
 
888
930
  begin
889
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
931
+ with_continuation_timeout do
890
932
  @last_basic_consume_ok = wait_on_continuations
891
933
  end
892
934
  rescue Exception => e
@@ -921,11 +963,13 @@ module Bunny
921
963
  def basic_cancel(consumer_tag)
922
964
  @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
923
965
 
924
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
966
+ with_continuation_timeout do
925
967
  @last_basic_cancel_ok = wait_on_continuations
926
968
  end
927
969
 
928
- maybe_kill_consumer_work_pool! unless any_consumers?
970
+ # reduces thread usage for channels that don't have any
971
+ # consumers
972
+ @work_pool.shutdown(true) unless self.any_consumers?
929
973
 
930
974
  @last_basic_cancel_ok
931
975
  end
@@ -943,7 +987,8 @@ module Bunny
943
987
 
944
988
  # Declares a queue using queue.declare AMQP 0.9.1 method.
945
989
  #
946
- # @param [String] name Queue name
990
+ # @param [String] name The name of the queue or an empty string to let RabbitMQ generate a name.
991
+ # Note that LF and CR characters will be stripped from the value.
947
992
  # @param [Hash] opts Queue properties
948
993
  #
949
994
  # @option opts [Boolean] durable (false) Should information about this queue be persisted to disk so that it
@@ -961,16 +1006,28 @@ module Bunny
961
1006
  def queue_declare(name, opts = {})
962
1007
  raise_if_no_longer_open!
963
1008
 
964
- @connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
965
- name,
1009
+ # strip trailing new line and carriage returns
1010
+ # just like RabbitMQ does
1011
+ safe_name = name.gsub(/[\r\n]/, "")
1012
+ @pending_queue_declare_name = safe_name
1013
+ @connection.send_frame(
1014
+ AMQ::Protocol::Queue::Declare.encode(@id,
1015
+ @pending_queue_declare_name,
966
1016
  opts.fetch(:passive, false),
967
1017
  opts.fetch(:durable, false),
968
1018
  opts.fetch(:exclusive, false),
969
1019
  opts.fetch(:auto_delete, false),
970
1020
  false,
971
1021
  opts[:arguments]))
972
- @last_queue_declare_ok = wait_on_continuations
973
1022
 
1023
+ begin
1024
+ with_continuation_timeout do
1025
+ @last_queue_declare_ok = wait_on_continuations
1026
+ end
1027
+ ensure
1028
+ # clear pending continuation context if it belongs to us
1029
+ @pending_queue_declare_name = nil if @pending_queue_declare_name == safe_name
1030
+ end
974
1031
  raise_if_continuation_resulted_in_a_channel_error!
975
1032
 
976
1033
  @last_queue_declare_ok
@@ -995,7 +1052,7 @@ module Bunny
995
1052
  opts[:if_unused],
996
1053
  opts[:if_empty],
997
1054
  false))
998
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1055
+ with_continuation_timeout do
999
1056
  @last_queue_delete_ok = wait_on_continuations
1000
1057
  end
1001
1058
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1015,7 +1072,7 @@ module Bunny
1015
1072
 
1016
1073
  @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
1017
1074
 
1018
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1075
+ with_continuation_timeout do
1019
1076
  @last_queue_purge_ok = wait_on_continuations
1020
1077
  end
1021
1078
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1051,7 +1108,7 @@ module Bunny
1051
1108
  (opts[:routing_key] || opts[:key]),
1052
1109
  false,
1053
1110
  opts[:arguments]))
1054
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1111
+ with_continuation_timeout do
1055
1112
  @last_queue_bind_ok = wait_on_continuations
1056
1113
  end
1057
1114
 
@@ -1086,7 +1143,7 @@ module Bunny
1086
1143
  exchange_name,
1087
1144
  opts[:routing_key],
1088
1145
  opts[:arguments]))
1089
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1146
+ with_continuation_timeout do
1090
1147
  @last_queue_unbind_ok = wait_on_continuations
1091
1148
  end
1092
1149
 
@@ -1099,25 +1156,30 @@ module Bunny
1099
1156
 
1100
1157
  # @group Exchange operations (exchange.*)
1101
1158
 
1102
- # Declares a echange using echange.declare AMQP 0.9.1 method.
1159
+ # Declares a exchange using exchange.declare AMQP 0.9.1 method.
1103
1160
  #
1104
- # @param [String] name Exchange name
1161
+ # @param [String] name The name of the exchange. Note that LF and CR characters
1162
+ # will be stripped from the value.
1163
+ # @param [String,Symbol] type Exchange type, e.g. :fanout or :topic
1105
1164
  # @param [Hash] opts Exchange properties
1106
1165
  #
1107
- # @option opts [Boolean] durable (false) Should information about this echange be persisted to disk so that it
1166
+ # @option opts [Boolean] durable (false) Should information about this exchange be persisted to disk so that it
1108
1167
  # can survive broker restarts? Typically set to true for long-lived exchanges.
1109
- # @option opts [Boolean] auto_delete (false) Should this echange be deleted when it is no longer used?
1168
+ # @option opts [Boolean] auto_delete (false) Should this exchange be deleted when it is no longer used?
1110
1169
  # @option opts [Boolean] passive (false) If true, exchange will be checked for existence. If it does not
1111
1170
  # exist, {Bunny::NotFound} will be raised.
1112
1171
  #
1113
1172
  # @return [AMQ::Protocol::Exchange::DeclareOk] RabbitMQ response
1114
- # @see http://rubybunny.info/articles/echanges.html Exchanges and Publishing guide
1173
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1115
1174
  # @api public
1116
1175
  def exchange_declare(name, type, opts = {})
1117
1176
  raise_if_no_longer_open!
1118
1177
 
1178
+ # strip trailing new line and carriage returns
1179
+ # just like RabbitMQ does
1180
+ safe_name = name.gsub(/[\r\n]/, "")
1119
1181
  @connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
1120
- name,
1182
+ safe_name,
1121
1183
  type.to_s,
1122
1184
  opts.fetch(:passive, false),
1123
1185
  opts.fetch(:durable, false),
@@ -1125,7 +1187,7 @@ module Bunny
1125
1187
  opts.fetch(:internal, false),
1126
1188
  false, # nowait
1127
1189
  opts[:arguments]))
1128
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1190
+ with_continuation_timeout do
1129
1191
  @last_exchange_declare_ok = wait_on_continuations
1130
1192
  end
1131
1193
 
@@ -1150,7 +1212,7 @@ module Bunny
1150
1212
  name,
1151
1213
  opts[:if_unused],
1152
1214
  false))
1153
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1215
+ with_continuation_timeout do
1154
1216
  @last_exchange_delete_ok = wait_on_continuations
1155
1217
  end
1156
1218
 
@@ -1194,7 +1256,7 @@ module Bunny
1194
1256
  opts[:routing_key],
1195
1257
  false,
1196
1258
  opts[:arguments]))
1197
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1259
+ with_continuation_timeout do
1198
1260
  @last_exchange_bind_ok = wait_on_continuations
1199
1261
  end
1200
1262
 
@@ -1238,7 +1300,7 @@ module Bunny
1238
1300
  opts[:routing_key],
1239
1301
  false,
1240
1302
  opts[:arguments]))
1241
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1303
+ with_continuation_timeout do
1242
1304
  @last_exchange_unbind_ok = wait_on_continuations
1243
1305
  end
1244
1306
 
@@ -1266,7 +1328,7 @@ module Bunny
1266
1328
  raise_if_no_longer_open!
1267
1329
 
1268
1330
  @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
1269
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1331
+ with_continuation_timeout do
1270
1332
  @last_channel_flow_ok = wait_on_continuations
1271
1333
  end
1272
1334
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1287,7 +1349,7 @@ module Bunny
1287
1349
  raise_if_no_longer_open!
1288
1350
 
1289
1351
  @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
1290
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1352
+ with_continuation_timeout do
1291
1353
  @last_tx_select_ok = wait_on_continuations
1292
1354
  end
1293
1355
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1303,7 +1365,7 @@ module Bunny
1303
1365
  raise_if_no_longer_open!
1304
1366
 
1305
1367
  @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
1306
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1368
+ with_continuation_timeout do
1307
1369
  @last_tx_commit_ok = wait_on_continuations
1308
1370
  end
1309
1371
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1318,7 +1380,7 @@ module Bunny
1318
1380
  raise_if_no_longer_open!
1319
1381
 
1320
1382
  @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
1321
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1383
+ with_continuation_timeout do
1322
1384
  @last_tx_rollback_ok = wait_on_continuations
1323
1385
  end
1324
1386
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1351,7 +1413,7 @@ module Bunny
1351
1413
  # @see #nacked_set
1352
1414
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1353
1415
  # @api public
1354
- def confirm_select(callback=nil)
1416
+ def confirm_select(callback = nil)
1355
1417
  raise_if_no_longer_open!
1356
1418
 
1357
1419
  if @next_publish_seq_no == 0
@@ -1359,33 +1421,32 @@ module Bunny
1359
1421
  @unconfirmed_set = Set.new
1360
1422
  @nacked_set = Set.new
1361
1423
  @next_publish_seq_no = 1
1424
+ @only_acks_received = true
1362
1425
  end
1363
1426
 
1364
1427
  @confirms_callback = callback
1365
1428
 
1366
1429
  @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
1367
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1430
+ with_continuation_timeout do
1368
1431
  @last_confirm_select_ok = wait_on_continuations
1369
1432
  end
1370
- @confirm_mode = true
1371
1433
  raise_if_continuation_resulted_in_a_channel_error!
1372
1434
  @last_confirm_select_ok
1373
1435
  end
1374
1436
 
1375
1437
  # Blocks calling thread until confirms are received for all
1376
- # currently unacknowledged published messages.
1438
+ # currently unacknowledged published messages. Returns immediately
1439
+ # if there are no outstanding confirms.
1377
1440
  #
1378
- # @return [Boolean] true if all messages were acknowledged positively, false otherwise
1441
+ # @return [Boolean] true if all messages were acknowledged positively since the last time this method was called, false otherwise
1379
1442
  # @see #confirm_select
1380
1443
  # @see #unconfirmed_set
1381
1444
  # @see #nacked_set
1382
1445
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1383
1446
  # @api public
1384
1447
  def wait_for_confirms
1385
- @only_acks_received = true
1386
1448
  wait_on_confirms_continuations
1387
-
1388
- @only_acks_received
1449
+ read_and_reset_only_acks_received
1389
1450
  end
1390
1451
 
1391
1452
  # @endgroup
@@ -1441,7 +1502,7 @@ module Bunny
1441
1502
  #
1442
1503
  # @api plugin
1443
1504
  def recover_from_network_failure
1444
- @logger.debug "Recovering channel #{@id} after network failure"
1505
+ @logger.debug { "Recovering channel #{@id} after network failure" }
1445
1506
  release_all_continuations
1446
1507
 
1447
1508
  recover_prefetch_setting
@@ -1459,7 +1520,7 @@ module Bunny
1459
1520
  #
1460
1521
  # @api plugin
1461
1522
  def recover_prefetch_setting
1462
- basic_qos(@prefetch_count) if @prefetch_count
1523
+ basic_qos(@prefetch_count, @prefetch_global) if @prefetch_count
1463
1524
  end
1464
1525
 
1465
1526
  # Recovers publisher confirms mode. Used by the Automatic Network Failure
@@ -1467,7 +1528,11 @@ module Bunny
1467
1528
  #
1468
1529
  # @api plugin
1469
1530
  def recover_confirm_mode
1470
- confirm_select if @confirm_mode
1531
+ if using_publisher_confirmations?
1532
+ @unconfirmed_set.clear
1533
+ @delivery_tag_offset = @next_publish_seq_no - 1
1534
+ confirm_select(@confirms_callback)
1535
+ end
1471
1536
  end
1472
1537
 
1473
1538
  # Recovers transaction mode. Used by the Automatic Network Failure
@@ -1483,7 +1548,7 @@ module Bunny
1483
1548
  #
1484
1549
  # @api plugin
1485
1550
  def recover_exchanges
1486
- @exchanges.values.dup.each do |x|
1551
+ @exchange_mutex.synchronize { @exchanges.values }.each do |x|
1487
1552
  x.recover_from_network_failure
1488
1553
  end
1489
1554
  end
@@ -1493,8 +1558,8 @@ module Bunny
1493
1558
  #
1494
1559
  # @api plugin
1495
1560
  def recover_queues
1496
- @queues.values.dup.each do |q|
1497
- @logger.debug "Recovering queue #{q.name}"
1561
+ @queue_mutex.synchronize { @queues.values }.each do |q|
1562
+ @logger.debug { "Recovering queue #{q.name}" }
1498
1563
  q.recover_from_network_failure
1499
1564
  end
1500
1565
  end
@@ -1505,10 +1570,11 @@ module Bunny
1505
1570
  # @api plugin
1506
1571
  def recover_consumers
1507
1572
  unless @consumers.empty?
1508
- @work_pool = ConsumerWorkPool.new(@work_pool.size)
1573
+ @work_pool = ConsumerWorkPool.new(@work_pool.size, @work_pool.abort_on_exception)
1509
1574
  @work_pool.start
1510
1575
  end
1511
- @consumers.values.dup.each do |c|
1576
+
1577
+ @consumer_mutex.synchronize { @consumers.values }.each do |c|
1512
1578
  c.recover_from_network_failure
1513
1579
  end
1514
1580
  end
@@ -1533,7 +1599,11 @@ module Bunny
1533
1599
 
1534
1600
  # @return [String] Brief human-readable representation of the channel
1535
1601
  def to_s
1536
- "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}>"
1602
+ "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}> @open=#{open?}"
1603
+ end
1604
+
1605
+ def inspect
1606
+ to_s
1537
1607
  end
1538
1608
 
1539
1609
 
@@ -1541,6 +1611,11 @@ module Bunny
1541
1611
  # Implementation
1542
1612
  #
1543
1613
 
1614
+ # @private
1615
+ def with_continuation_timeout(&block)
1616
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout, &block)
1617
+ end
1618
+
1544
1619
  # @private
1545
1620
  def register_consumer(consumer_tag, consumer)
1546
1621
  @consumer_mutex.synchronize do
@@ -1564,12 +1639,33 @@ module Bunny
1564
1639
  end
1565
1640
  end
1566
1641
 
1642
+ # @private
1643
+ def pending_server_named_queue_declaration?
1644
+ @pending_queue_declare_name && @pending_queue_declare_name.empty?
1645
+ end
1646
+
1647
+ # @private
1648
+ def can_accept_queue_declare_ok?(method)
1649
+ @pending_queue_declare_name == method.queue ||
1650
+ pending_server_named_queue_declaration?
1651
+ end
1652
+
1567
1653
  # @private
1568
1654
  def handle_method(method)
1569
- @logger.debug "Channel#handle_frame on channel #{@id}: #{method.inspect}"
1655
+ @logger.debug { "Channel#handle_frame on channel #{@id}: #{method.inspect}" }
1570
1656
  case method
1571
1657
  when AMQ::Protocol::Queue::DeclareOk then
1572
- @continuations.push(method)
1658
+ # safeguard against late arrivals of responses and
1659
+ # so on, see ruby-amqp/bunny#558
1660
+ if can_accept_queue_declare_ok?(method)
1661
+ @continuations.push(method)
1662
+ else
1663
+ if !pending_server_named_queue_declaration?
1664
+ # this response is for an outdated/overwritten
1665
+ # queue.declare, drop it
1666
+ @logger.warn "Received a queue.declare-ok response for a mismatching queue (#{method.queue} instead of #{@pending_queue_declare_name}) on channel #{@id} possibly due to a timeout, ignoring it"
1667
+ end
1668
+ end
1573
1669
  when AMQ::Protocol::Queue::DeleteOk then
1574
1670
  @continuations.push(method)
1575
1671
  when AMQ::Protocol::Queue::PurgeOk then
@@ -1691,28 +1787,26 @@ module Bunny
1691
1787
  end
1692
1788
 
1693
1789
  # @private
1694
- def handle_ack_or_nack(delivery_tag, multiple, nack)
1790
+ def handle_ack_or_nack(delivery_tag_before_offset, multiple, nack)
1791
+ delivery_tag = delivery_tag_before_offset + @delivery_tag_offset
1792
+ confirmed_range_start = multiple ? @delivery_tag_offset + 1 : delivery_tag
1793
+ confirmed_range_end = delivery_tag
1794
+ confirmed_range = (confirmed_range_start..confirmed_range_end)
1795
+
1695
1796
  @unconfirmed_set_mutex.synchronize do
1696
1797
  if nack
1697
- cloned_set = @unconfirmed_set.clone
1698
- if multiple
1699
- cloned_set.keep_if { |i| i <= delivery_tag }
1700
- @nacked_set.merge(cloned_set)
1701
- else
1702
- @nacked_set.add(delivery_tag)
1703
- end
1798
+ @nacked_set.merge(@unconfirmed_set & confirmed_range)
1704
1799
  end
1705
1800
 
1706
- if multiple
1707
- @unconfirmed_set.delete_if { |i| i <= delivery_tag }
1708
- else
1709
- @unconfirmed_set.delete(delivery_tag)
1710
- end
1801
+ @unconfirmed_set.subtract(confirmed_range)
1711
1802
 
1712
1803
  @only_acks_received = (@only_acks_received && !nack)
1713
1804
 
1714
1805
  @confirms_continuations.push(true) if @unconfirmed_set.empty?
1715
- @confirms_callback.call(delivery_tag, multiple, nack) if @confirms_callback
1806
+
1807
+ if @confirms_callback
1808
+ confirmed_range.each { |tag| @confirms_callback.call(tag, false, nack) }
1809
+ end
1716
1810
  end
1717
1811
  end
1718
1812
 
@@ -1754,22 +1848,37 @@ module Bunny
1754
1848
 
1755
1849
  # @private
1756
1850
  def wait_on_confirms_continuations
1851
+ raise_if_no_longer_open!
1852
+
1757
1853
  if @connection.threaded
1758
1854
  t = Thread.current
1759
1855
  @threads_waiting_on_confirms_continuations << t
1760
1856
 
1761
1857
  begin
1762
- @confirms_continuations.poll(@connection.continuation_timeout)
1858
+ while @unconfirmed_set_mutex.synchronize { !@unconfirmed_set.empty? }
1859
+ @confirms_continuations.poll(@connection.continuation_timeout)
1860
+ end
1763
1861
  ensure
1764
1862
  @threads_waiting_on_confirms_continuations.delete(t)
1765
1863
  end
1766
1864
  else
1767
- connection.reader_loop.run_once until @confirms_continuations.length > 0
1865
+ unless @unconfirmed_set.empty?
1866
+ connection.reader_loop.run_once until @confirms_continuations.length > 0
1867
+ @confirms_continuations.pop
1868
+ end
1869
+ end
1870
+ end
1768
1871
 
1769
- @confirms_continuations.pop
1872
+ # @private
1873
+ def read_and_reset_only_acks_received
1874
+ @unconfirmed_set_mutex.synchronize do
1875
+ result = @only_acks_received
1876
+ @only_acks_received = true
1877
+ result
1770
1878
  end
1771
1879
  end
1772
1880
 
1881
+
1773
1882
  # Releases all continuations. Used by automatic network recovery.
1774
1883
  # @private
1775
1884
  def release_all_continuations
@@ -1815,37 +1924,37 @@ module Bunny
1815
1924
 
1816
1925
  # @private
1817
1926
  def deregister_queue(queue)
1818
- @queues.delete(queue.name)
1927
+ @queue_mutex.synchronize { @queues.delete(queue.name) }
1819
1928
  end
1820
1929
 
1821
1930
  # @private
1822
1931
  def deregister_queue_named(name)
1823
- @queues.delete(name)
1932
+ @queue_mutex.synchronize { @queues.delete(name) }
1824
1933
  end
1825
1934
 
1826
1935
  # @private
1827
1936
  def register_queue(queue)
1828
- @queues[queue.name] = queue
1937
+ @queue_mutex.synchronize { @queues[queue.name] = queue }
1829
1938
  end
1830
1939
 
1831
1940
  # @private
1832
1941
  def find_queue(name)
1833
- @queues[name]
1942
+ @queue_mutex.synchronize { @queues[name] }
1834
1943
  end
1835
1944
 
1836
1945
  # @private
1837
1946
  def deregister_exchange(exchange)
1838
- @exchanges.delete(exchange.name)
1947
+ @exchange_mutex.synchronize { @exchanges.delete(exchange.name) }
1839
1948
  end
1840
1949
 
1841
1950
  # @private
1842
1951
  def register_exchange(exchange)
1843
- @exchanges[exchange.name] = exchange
1952
+ @exchange_mutex.synchronize { @exchanges[exchange.name] = exchange }
1844
1953
  end
1845
1954
 
1846
1955
  # @private
1847
1956
  def find_exchange(name)
1848
- @exchanges[name]
1957
+ @exchange_mutex.synchronize { @exchanges[name] }
1849
1958
  end
1850
1959
 
1851
1960
  protected
@@ -1885,7 +1994,13 @@ module Bunny
1885
1994
 
1886
1995
  # @private
1887
1996
  def raise_if_no_longer_open!
1888
- raise ChannelAlreadyClosed.new("cannot use a channel that was already closed! Channel id: #{@id}", self) if closed?
1997
+ if closed?
1998
+ if @last_channel_error
1999
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}, closed due to a server-reported channel error: #{@last_channel_error.message}", self)
2000
+ else
2001
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}", self)
2002
+ end
2003
+ end
1889
2004
  end
1890
2005
 
1891
2006
  # @private
@@ -1925,7 +2040,7 @@ module Bunny
1925
2040
  def guarding_against_stale_delivery_tags(tag, &block)
1926
2041
  case tag
1927
2042
  # if a fixnum was passed, execute unconditionally. MK.
1928
- when Fixnum then
2043
+ when Integer then
1929
2044
  block.call
1930
2045
  # versioned delivery tags should be checked to avoid
1931
2046
  # sending out stale (invalid) tags after channel was reopened