bunny 1.7.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 (141) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +6 -1
  4. data/.rspec +1 -3
  5. data/.travis.yml +21 -14
  6. data/CONTRIBUTING.md +132 -0
  7. data/ChangeLog.md +745 -1
  8. data/Gemfile +13 -13
  9. data/LICENSE +1 -1
  10. data/README.md +41 -75
  11. data/Rakefile +54 -0
  12. data/bunny.gemspec +4 -10
  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/getting_started/hello_world.rb +2 -0
  30. data/examples/guides/getting_started/weathr.rb +2 -0
  31. data/examples/guides/queues/one_off_consumer.rb +2 -0
  32. data/examples/guides/queues/redeliveries.rb +2 -0
  33. data/lib/bunny.rb +6 -2
  34. data/lib/bunny/channel.rb +192 -109
  35. data/lib/bunny/channel_id_allocator.rb +6 -4
  36. data/lib/bunny/concurrent/continuation_queue.rb +34 -13
  37. data/lib/bunny/consumer_work_pool.rb +34 -6
  38. data/lib/bunny/cruby/socket.rb +29 -16
  39. data/lib/bunny/cruby/ssl_socket.rb +20 -7
  40. data/lib/bunny/exceptions.rb +7 -1
  41. data/lib/bunny/exchange.rb +11 -7
  42. data/lib/bunny/get_response.rb +1 -1
  43. data/lib/bunny/heartbeat_sender.rb +3 -2
  44. data/lib/bunny/jruby/socket.rb +23 -6
  45. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  46. data/lib/bunny/queue.rb +12 -10
  47. data/lib/bunny/reader_loop.rb +31 -18
  48. data/lib/bunny/session.rb +389 -134
  49. data/lib/bunny/test_kit.rb +14 -0
  50. data/lib/bunny/timeout.rb +1 -12
  51. data/lib/bunny/transport.rb +114 -67
  52. data/lib/bunny/version.rb +1 -1
  53. data/repl +1 -1
  54. data/spec/config/rabbitmq.conf +13 -0
  55. data/spec/higher_level_api/integration/basic_ack_spec.rb +154 -22
  56. data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
  57. data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
  58. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
  59. data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
  60. data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
  61. data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
  62. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
  63. data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
  64. data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
  65. data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
  66. data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
  67. data/spec/higher_level_api/integration/connection_recovery_spec.rb +412 -286
  68. data/spec/higher_level_api/integration/connection_spec.rb +284 -134
  69. data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
  70. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
  71. data/spec/higher_level_api/integration/dead_lettering_spec.rb +14 -14
  72. data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
  73. data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
  74. data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
  75. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
  76. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
  77. data/spec/higher_level_api/integration/heartbeat_spec.rb +4 -4
  78. data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
  79. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
  80. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +92 -27
  81. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
  82. data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
  83. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
  84. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  85. data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
  86. data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
  87. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
  88. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
  89. data/spec/higher_level_api/integration/tls_connection_spec.rb +218 -112
  90. data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
  91. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  92. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  93. data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
  94. data/spec/issues/issue100_spec.rb +11 -12
  95. data/spec/issues/issue141_spec.rb +13 -14
  96. data/spec/issues/issue202_spec.rb +1 -1
  97. data/spec/issues/issue224_spec.rb +5 -5
  98. data/spec/issues/issue465_spec.rb +32 -0
  99. data/spec/issues/issue549_spec.rb +30 -0
  100. data/spec/issues/issue78_spec.rb +21 -24
  101. data/spec/issues/issue83_spec.rb +5 -6
  102. data/spec/issues/issue97_spec.rb +44 -45
  103. data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
  104. data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
  105. data/spec/spec_helper.rb +2 -19
  106. data/spec/stress/channel_close_stress_spec.rb +3 -3
  107. data/spec/stress/channel_open_stress_spec.rb +4 -4
  108. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
  109. data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
  110. data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
  111. data/spec/stress/connection_open_close_spec.rb +9 -9
  112. data/spec/stress/merry_go_round_spec.rb +105 -0
  113. data/spec/tls/ca_certificate.pem +27 -16
  114. data/spec/tls/ca_key.pem +52 -27
  115. data/spec/tls/client_certificate.pem +27 -16
  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_certificate.pem +27 -16
  121. data/spec/tls/server_key.pem +49 -25
  122. data/spec/toxiproxy_helper.rb +28 -0
  123. data/spec/unit/bunny_spec.rb +5 -5
  124. data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
  125. data/spec/unit/concurrent/condition_spec.rb +8 -8
  126. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
  127. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
  128. data/spec/unit/exchange_recovery_spec.rb +39 -0
  129. data/spec/unit/version_delivery_tag_spec.rb +3 -3
  130. metadata +42 -35
  131. data/lib/bunny/system_timer.rb +0 -20
  132. data/spec/config/rabbitmq.config +0 -18
  133. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  134. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  135. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  136. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  137. data/spec/stress/long_running_consumer_spec.rb +0 -83
  138. data/spec/tls/cacert.pem +0 -18
  139. data/spec/tls/client_cert.pem +0 -18
  140. data/spec/tls/server_cert.pem +0 -18
  141. 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
 
@@ -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
 
@@ -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
@@ -156,6 +156,8 @@ module Bunny
156
156
 
157
157
  # @return [Integer] active basic.qos prefetch value
158
158
  attr_reader :prefetch_count
159
+ # @return [Integer] active basic.qos prefetch global mode
160
+ attr_reader :prefetch_global
159
161
 
160
162
  DEFAULT_CONTENT_TYPE = "application/octet-stream".freeze
161
163
  SHORTSTR_LIMIT = 255
@@ -167,6 +169,17 @@ module Bunny
167
169
  @connection = connection
168
170
  @logger = connection.logger
169
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
+
170
183
  @status = :opening
171
184
 
172
185
  @connection.register_channel(self)
@@ -180,6 +193,9 @@ module Bunny
180
193
  @publishing_mutex = @connection.mutex_impl.new
181
194
  @consumer_mutex = @connection.mutex_impl.new
182
195
 
196
+ @queue_mutex = @connection.mutex_impl.new
197
+ @exchange_mutex = @connection.mutex_impl.new
198
+
183
199
  @unconfirmed_set_mutex = @connection.mutex_impl.new
184
200
 
185
201
  self.reset_continuations
@@ -228,6 +244,9 @@ module Bunny
228
244
  # {Bunny::Queue}, {Bunny::Exchange} and {Bunny::Consumer} instances.
229
245
  # @api public
230
246
  def close
247
+ # see bunny#528
248
+ raise_if_no_longer_open!
249
+
231
250
  @connection.close_channel(self)
232
251
  @status = :closed
233
252
  @work_pool.shutdown
@@ -246,7 +265,6 @@ module Bunny
246
265
  @status == :closed
247
266
  end
248
267
 
249
-
250
268
  #
251
269
  # @group Backwards compatibility with 0.8.0
252
270
  #
@@ -295,7 +313,7 @@ module Bunny
295
313
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
296
314
  # @api public
297
315
  def fanout(name, opts = {})
298
- Exchange.new(self, :fanout, name, opts)
316
+ find_exchange(name) || Exchange.new(self, :fanout, name, opts)
299
317
  end
300
318
 
301
319
  # Declares a direct exchange or looks it up in the cache of previously
@@ -313,7 +331,7 @@ module Bunny
313
331
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
314
332
  # @api public
315
333
  def direct(name, opts = {})
316
- Exchange.new(self, :direct, name, opts)
334
+ find_exchange(name) || Exchange.new(self, :direct, name, opts)
317
335
  end
318
336
 
319
337
  # Declares a topic exchange or looks it up in the cache of previously
@@ -331,7 +349,7 @@ module Bunny
331
349
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
332
350
  # @api public
333
351
  def topic(name, opts = {})
334
- Exchange.new(self, :topic, name, opts)
352
+ find_exchange(name) || Exchange.new(self, :topic, name, opts)
335
353
  end
336
354
 
337
355
  # Declares a headers exchange or looks it up in the cache of previously
@@ -349,14 +367,14 @@ module Bunny
349
367
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
350
368
  # @api public
351
369
  def headers(name, opts = {})
352
- Exchange.new(self, :headers, name, opts)
370
+ find_exchange(name) || Exchange.new(self, :headers, name, opts)
353
371
  end
354
372
 
355
373
  # Provides access to the default exchange
356
374
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
357
375
  # @api public
358
376
  def default_exchange
359
- self.direct(AMQ::Protocol::EMPTY_STRING, :no_declare => true)
377
+ Exchange.default(self)
360
378
  end
361
379
 
362
380
  # Declares a headers exchange or looks it up in the cache of previously
@@ -397,6 +415,8 @@ module Bunny
397
415
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
398
416
  # @api public
399
417
  def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {})
418
+ throw ArgumentError.new("queue name must not be nil") if name.nil?
419
+
400
420
  q = find_queue(name) || Bunny::Queue.new(self, name, opts)
401
421
 
402
422
  register_queue(q)
@@ -417,17 +437,6 @@ module Bunny
417
437
 
418
438
  # @group QoS and Flow Control
419
439
 
420
- # Sets how many messages will be given to consumers on this channel before they
421
- # have to acknowledge or reject one of the previously consumed messages
422
- #
423
- # @param [Integer] prefetch_count Prefetch (QoS setting) for this channel
424
- # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
425
- # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
426
- # @api public
427
- def prefetch(count)
428
- self.basic_qos(count, false)
429
- end
430
-
431
440
  # Flow control. When set to false, RabbitMQ will stop delivering messages on this
432
441
  # channel.
433
442
  #
@@ -460,9 +469,7 @@ module Bunny
460
469
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
461
470
  # @api public
462
471
  def reject(delivery_tag, requeue = false)
463
- guarding_against_stale_delivery_tags(delivery_tag) do
464
- basic_reject(delivery_tag.to_i, requeue)
465
- end
472
+ basic_reject(delivery_tag.to_i, requeue)
466
473
  end
467
474
 
468
475
  # Acknowledges a message. Acknowledged messages are completely removed from the queue.
@@ -473,9 +480,7 @@ module Bunny
473
480
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
474
481
  # @api public
475
482
  def ack(delivery_tag, multiple = false)
476
- guarding_against_stale_delivery_tags(delivery_tag) do
477
- basic_ack(delivery_tag.to_i, multiple)
478
- end
483
+ basic_ack(delivery_tag.to_i, multiple)
479
484
  end
480
485
  alias acknowledge ack
481
486
 
@@ -490,9 +495,7 @@ module Bunny
490
495
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
491
496
  # @api public
492
497
  def nack(delivery_tag, multiple = false, requeue = false)
493
- guarding_against_stale_delivery_tags(delivery_tag) do
494
- basic_nack(delivery_tag.to_i, multiple, requeue)
495
- end
498
+ basic_nack(delivery_tag.to_i, multiple, requeue)
496
499
  end
497
500
 
498
501
  # @endgroup
@@ -562,7 +565,7 @@ module Bunny
562
565
  opts[:mandatory],
563
566
  false,
564
567
  @connection.frame_max)
565
- @connection.send_frameset_without_timeout(frames, self)
568
+ @connection.send_frameset(frames, self)
566
569
 
567
570
  self
568
571
  end
@@ -605,40 +608,59 @@ module Bunny
605
608
  # implementation (and even more correct and convenient ones, such as wait/notify, should
606
609
  # we implement them). So we return a triple of nils immediately which apps should be
607
610
  # able to handle anyway as "got no message, no need to act". MK.
608
- @last_basic_get_response = if @connection.open?
609
- wait_on_basic_get_continuations
610
- else
611
- [nil, nil, nil]
612
- 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
613
621
 
614
622
  raise_if_continuation_resulted_in_a_channel_error!
615
- @last_basic_get_response
623
+ last_basic_get_response
616
624
  end
617
625
 
626
+ # prefetch_count is of type short in the protocol. MK.
627
+ MAX_PREFETCH_COUNT = (2 ** 16) - 1
628
+
618
629
  # Controls message delivery rate using basic.qos AMQP 0.9.1 method.
619
630
  #
620
631
  # @param [Integer] prefetch_count How many messages can consumers on this channel be given at a time
621
632
  # (before they have to acknowledge or reject one of the earlier received messages)
622
- # @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).
623
642
  # @return [AMQ::Protocol::Basic::QosOk] RabbitMQ response
624
643
  # @see Bunny::Channel#prefetch
625
644
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
626
645
  # @api public
627
646
  def basic_qos(count, global = false)
628
- 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
629
649
  raise_if_no_longer_open!
630
650
 
631
651
  @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
632
652
 
633
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
653
+ with_continuation_timeout do
634
654
  @last_basic_qos_ok = wait_on_continuations
635
655
  end
636
656
  raise_if_continuation_resulted_in_a_channel_error!
637
657
 
638
- @prefetch_count = count
658
+ @prefetch_count = count
659
+ @prefetch_global = global
639
660
 
640
661
  @last_basic_qos_ok
641
662
  end
663
+ alias prefetch basic_qos
642
664
 
643
665
  # Redeliver unacknowledged messages
644
666
  #
@@ -649,7 +671,7 @@ module Bunny
649
671
  raise_if_no_longer_open!
650
672
 
651
673
  @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
652
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
674
+ with_continuation_timeout do
653
675
  @last_basic_recover_ok = wait_on_continuations
654
676
  end
655
677
  raise_if_continuation_resulted_in_a_channel_error!
@@ -679,7 +701,7 @@ module Bunny
679
701
  #
680
702
  # ch = conn.create_channel
681
703
  # q.subscribe do |delivery_info, properties, payload|
682
- # # requeue the message
704
+ # # reject the message
683
705
  # ch.basic_reject(delivery_info.delivery_tag, false)
684
706
  # end
685
707
  #
@@ -695,11 +717,13 @@ module Bunny
695
717
  # @see Bunny::Channel#basic_nack
696
718
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
697
719
  # @api public
698
- def basic_reject(delivery_tag, requeue)
699
- raise_if_no_longer_open!
700
- @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))
701
724
 
702
- nil
725
+ nil
726
+ end
703
727
  end
704
728
 
705
729
  # Acknowledges a delivery (message).
@@ -715,7 +739,7 @@ module Bunny
715
739
  # ch = conn.create_channel
716
740
  # q.subscribe do |delivery_info, properties, payload|
717
741
  # # requeue the message
718
- # ch.basic_ack(delivery_info.delivery_tag)
742
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
719
743
  # end
720
744
  #
721
745
  # @example Ack a message fetched via basic.get
@@ -725,7 +749,7 @@ module Bunny
725
749
  # ch = conn.create_channel
726
750
  # # we assume the queue exists and has messages
727
751
  # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
728
- # ch.basic_ack(delivery_info.delivery_tag)
752
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
729
753
  #
730
754
  # @example Ack multiple messages fetched via basic.get
731
755
  # conn = Bunny.new
@@ -737,16 +761,17 @@ module Bunny
737
761
  # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
738
762
  # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
739
763
  # # ack all fetched messages up to payload3
740
- # ch.basic_ack(delivery_info.delivery_tag, true)
764
+ # ch.basic_ack(delivery_info.delivery_tag.to_i, true)
741
765
  #
742
766
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
743
- # @see #basic_ack_known_delivery_tag
744
767
  # @api public
745
- def basic_ack(delivery_tag, multiple)
746
- raise_if_no_longer_open!
747
- @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))
748
772
 
749
- nil
773
+ nil
774
+ end
750
775
  end
751
776
 
752
777
  # Rejects or requeues messages just like {Bunny::Channel#basic_reject} but can do so
@@ -803,13 +828,15 @@ module Bunny
803
828
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
804
829
  # @api public
805
830
  def basic_nack(delivery_tag, multiple = false, requeue = false)
806
- raise_if_no_longer_open!
807
- @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
808
- delivery_tag,
809
- multiple,
810
- 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))
811
837
 
812
- nil
838
+ nil
839
+ end
813
840
  end
814
841
 
815
842
  # Registers a consumer for queue. Delivered messages will be handled with the block
@@ -851,7 +878,7 @@ module Bunny
851
878
  arguments))
852
879
 
853
880
  begin
854
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
881
+ with_continuation_timeout do
855
882
  @last_basic_consume_ok = wait_on_continuations
856
883
  end
857
884
  rescue Exception => e
@@ -901,7 +928,7 @@ module Bunny
901
928
  consumer.arguments))
902
929
 
903
930
  begin
904
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
931
+ with_continuation_timeout do
905
932
  @last_basic_consume_ok = wait_on_continuations
906
933
  end
907
934
  rescue Exception => e
@@ -936,11 +963,13 @@ module Bunny
936
963
  def basic_cancel(consumer_tag)
937
964
  @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
938
965
 
939
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
966
+ with_continuation_timeout do
940
967
  @last_basic_cancel_ok = wait_on_continuations
941
968
  end
942
969
 
943
- 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?
944
973
 
945
974
  @last_basic_cancel_ok
946
975
  end
@@ -958,7 +987,8 @@ module Bunny
958
987
 
959
988
  # Declares a queue using queue.declare AMQP 0.9.1 method.
960
989
  #
961
- # @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.
962
992
  # @param [Hash] opts Queue properties
963
993
  #
964
994
  # @option opts [Boolean] durable (false) Should information about this queue be persisted to disk so that it
@@ -976,16 +1006,28 @@ module Bunny
976
1006
  def queue_declare(name, opts = {})
977
1007
  raise_if_no_longer_open!
978
1008
 
979
- @connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
980
- 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,
981
1016
  opts.fetch(:passive, false),
982
1017
  opts.fetch(:durable, false),
983
1018
  opts.fetch(:exclusive, false),
984
1019
  opts.fetch(:auto_delete, false),
985
1020
  false,
986
1021
  opts[:arguments]))
987
- @last_queue_declare_ok = wait_on_continuations
988
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
989
1031
  raise_if_continuation_resulted_in_a_channel_error!
990
1032
 
991
1033
  @last_queue_declare_ok
@@ -1010,7 +1052,7 @@ module Bunny
1010
1052
  opts[:if_unused],
1011
1053
  opts[:if_empty],
1012
1054
  false))
1013
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1055
+ with_continuation_timeout do
1014
1056
  @last_queue_delete_ok = wait_on_continuations
1015
1057
  end
1016
1058
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1030,7 +1072,7 @@ module Bunny
1030
1072
 
1031
1073
  @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
1032
1074
 
1033
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1075
+ with_continuation_timeout do
1034
1076
  @last_queue_purge_ok = wait_on_continuations
1035
1077
  end
1036
1078
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1066,7 +1108,7 @@ module Bunny
1066
1108
  (opts[:routing_key] || opts[:key]),
1067
1109
  false,
1068
1110
  opts[:arguments]))
1069
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1111
+ with_continuation_timeout do
1070
1112
  @last_queue_bind_ok = wait_on_continuations
1071
1113
  end
1072
1114
 
@@ -1101,7 +1143,7 @@ module Bunny
1101
1143
  exchange_name,
1102
1144
  opts[:routing_key],
1103
1145
  opts[:arguments]))
1104
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1146
+ with_continuation_timeout do
1105
1147
  @last_queue_unbind_ok = wait_on_continuations
1106
1148
  end
1107
1149
 
@@ -1114,26 +1156,30 @@ module Bunny
1114
1156
 
1115
1157
  # @group Exchange operations (exchange.*)
1116
1158
 
1117
- # Declares a echange using echange.declare AMQP 0.9.1 method.
1159
+ # Declares a exchange using exchange.declare AMQP 0.9.1 method.
1118
1160
  #
1119
- # @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.
1120
1163
  # @param [String,Symbol] type Exchange type, e.g. :fanout or :topic
1121
1164
  # @param [Hash] opts Exchange properties
1122
1165
  #
1123
- # @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
1124
1167
  # can survive broker restarts? Typically set to true for long-lived exchanges.
1125
- # @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?
1126
1169
  # @option opts [Boolean] passive (false) If true, exchange will be checked for existence. If it does not
1127
1170
  # exist, {Bunny::NotFound} will be raised.
1128
1171
  #
1129
1172
  # @return [AMQ::Protocol::Exchange::DeclareOk] RabbitMQ response
1130
- # @see http://rubybunny.info/articles/echanges.html Exchanges and Publishing guide
1173
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1131
1174
  # @api public
1132
1175
  def exchange_declare(name, type, opts = {})
1133
1176
  raise_if_no_longer_open!
1134
1177
 
1178
+ # strip trailing new line and carriage returns
1179
+ # just like RabbitMQ does
1180
+ safe_name = name.gsub(/[\r\n]/, "")
1135
1181
  @connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
1136
- name,
1182
+ safe_name,
1137
1183
  type.to_s,
1138
1184
  opts.fetch(:passive, false),
1139
1185
  opts.fetch(:durable, false),
@@ -1141,7 +1187,7 @@ module Bunny
1141
1187
  opts.fetch(:internal, false),
1142
1188
  false, # nowait
1143
1189
  opts[:arguments]))
1144
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1190
+ with_continuation_timeout do
1145
1191
  @last_exchange_declare_ok = wait_on_continuations
1146
1192
  end
1147
1193
 
@@ -1166,7 +1212,7 @@ module Bunny
1166
1212
  name,
1167
1213
  opts[:if_unused],
1168
1214
  false))
1169
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1215
+ with_continuation_timeout do
1170
1216
  @last_exchange_delete_ok = wait_on_continuations
1171
1217
  end
1172
1218
 
@@ -1210,7 +1256,7 @@ module Bunny
1210
1256
  opts[:routing_key],
1211
1257
  false,
1212
1258
  opts[:arguments]))
1213
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1259
+ with_continuation_timeout do
1214
1260
  @last_exchange_bind_ok = wait_on_continuations
1215
1261
  end
1216
1262
 
@@ -1254,7 +1300,7 @@ module Bunny
1254
1300
  opts[:routing_key],
1255
1301
  false,
1256
1302
  opts[:arguments]))
1257
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1303
+ with_continuation_timeout do
1258
1304
  @last_exchange_unbind_ok = wait_on_continuations
1259
1305
  end
1260
1306
 
@@ -1282,7 +1328,7 @@ module Bunny
1282
1328
  raise_if_no_longer_open!
1283
1329
 
1284
1330
  @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
1285
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1331
+ with_continuation_timeout do
1286
1332
  @last_channel_flow_ok = wait_on_continuations
1287
1333
  end
1288
1334
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1303,7 +1349,7 @@ module Bunny
1303
1349
  raise_if_no_longer_open!
1304
1350
 
1305
1351
  @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
1306
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1352
+ with_continuation_timeout do
1307
1353
  @last_tx_select_ok = wait_on_continuations
1308
1354
  end
1309
1355
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1319,7 +1365,7 @@ module Bunny
1319
1365
  raise_if_no_longer_open!
1320
1366
 
1321
1367
  @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
1322
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1368
+ with_continuation_timeout do
1323
1369
  @last_tx_commit_ok = wait_on_continuations
1324
1370
  end
1325
1371
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1334,7 +1380,7 @@ module Bunny
1334
1380
  raise_if_no_longer_open!
1335
1381
 
1336
1382
  @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
1337
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1383
+ with_continuation_timeout do
1338
1384
  @last_tx_rollback_ok = wait_on_continuations
1339
1385
  end
1340
1386
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1367,7 +1413,7 @@ module Bunny
1367
1413
  # @see #nacked_set
1368
1414
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1369
1415
  # @api public
1370
- def confirm_select(callback=nil)
1416
+ def confirm_select(callback = nil)
1371
1417
  raise_if_no_longer_open!
1372
1418
 
1373
1419
  if @next_publish_seq_no == 0
@@ -1381,10 +1427,9 @@ module Bunny
1381
1427
  @confirms_callback = callback
1382
1428
 
1383
1429
  @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
1384
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1430
+ with_continuation_timeout do
1385
1431
  @last_confirm_select_ok = wait_on_continuations
1386
1432
  end
1387
- @confirm_mode = true
1388
1433
  raise_if_continuation_resulted_in_a_channel_error!
1389
1434
  @last_confirm_select_ok
1390
1435
  end
@@ -1457,7 +1502,7 @@ module Bunny
1457
1502
  #
1458
1503
  # @api plugin
1459
1504
  def recover_from_network_failure
1460
- @logger.debug "Recovering channel #{@id} after network failure"
1505
+ @logger.debug { "Recovering channel #{@id} after network failure" }
1461
1506
  release_all_continuations
1462
1507
 
1463
1508
  recover_prefetch_setting
@@ -1475,7 +1520,7 @@ module Bunny
1475
1520
  #
1476
1521
  # @api plugin
1477
1522
  def recover_prefetch_setting
1478
- basic_qos(@prefetch_count) if @prefetch_count
1523
+ basic_qos(@prefetch_count, @prefetch_global) if @prefetch_count
1479
1524
  end
1480
1525
 
1481
1526
  # Recovers publisher confirms mode. Used by the Automatic Network Failure
@@ -1484,6 +1529,7 @@ module Bunny
1484
1529
  # @api plugin
1485
1530
  def recover_confirm_mode
1486
1531
  if using_publisher_confirmations?
1532
+ @unconfirmed_set.clear
1487
1533
  @delivery_tag_offset = @next_publish_seq_no - 1
1488
1534
  confirm_select(@confirms_callback)
1489
1535
  end
@@ -1502,7 +1548,7 @@ module Bunny
1502
1548
  #
1503
1549
  # @api plugin
1504
1550
  def recover_exchanges
1505
- @exchanges.values.dup.each do |x|
1551
+ @exchange_mutex.synchronize { @exchanges.values }.each do |x|
1506
1552
  x.recover_from_network_failure
1507
1553
  end
1508
1554
  end
@@ -1512,8 +1558,8 @@ module Bunny
1512
1558
  #
1513
1559
  # @api plugin
1514
1560
  def recover_queues
1515
- @queues.values.dup.each do |q|
1516
- @logger.debug "Recovering queue #{q.name}"
1561
+ @queue_mutex.synchronize { @queues.values }.each do |q|
1562
+ @logger.debug { "Recovering queue #{q.name}" }
1517
1563
  q.recover_from_network_failure
1518
1564
  end
1519
1565
  end
@@ -1524,10 +1570,11 @@ module Bunny
1524
1570
  # @api plugin
1525
1571
  def recover_consumers
1526
1572
  unless @consumers.empty?
1527
- @work_pool = ConsumerWorkPool.new(@work_pool.size)
1573
+ @work_pool = ConsumerWorkPool.new(@work_pool.size, @work_pool.abort_on_exception)
1528
1574
  @work_pool.start
1529
1575
  end
1530
- @consumers.values.dup.each do |c|
1576
+
1577
+ @consumer_mutex.synchronize { @consumers.values }.each do |c|
1531
1578
  c.recover_from_network_failure
1532
1579
  end
1533
1580
  end
@@ -1552,7 +1599,11 @@ module Bunny
1552
1599
 
1553
1600
  # @return [String] Brief human-readable representation of the channel
1554
1601
  def to_s
1555
- "#<#{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
1556
1607
  end
1557
1608
 
1558
1609
 
@@ -1560,6 +1611,11 @@ module Bunny
1560
1611
  # Implementation
1561
1612
  #
1562
1613
 
1614
+ # @private
1615
+ def with_continuation_timeout(&block)
1616
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout, &block)
1617
+ end
1618
+
1563
1619
  # @private
1564
1620
  def register_consumer(consumer_tag, consumer)
1565
1621
  @consumer_mutex.synchronize do
@@ -1583,12 +1639,33 @@ module Bunny
1583
1639
  end
1584
1640
  end
1585
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
+
1586
1653
  # @private
1587
1654
  def handle_method(method)
1588
- @logger.debug "Channel#handle_frame on channel #{@id}: #{method.inspect}"
1655
+ @logger.debug { "Channel#handle_frame on channel #{@id}: #{method.inspect}" }
1589
1656
  case method
1590
1657
  when AMQ::Protocol::Queue::DeclareOk then
1591
- @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
1592
1669
  when AMQ::Protocol::Queue::DeleteOk then
1593
1670
  @continuations.push(method)
1594
1671
  when AMQ::Protocol::Queue::PurgeOk then
@@ -1771,16 +1848,16 @@ module Bunny
1771
1848
 
1772
1849
  # @private
1773
1850
  def wait_on_confirms_continuations
1851
+ raise_if_no_longer_open!
1852
+
1774
1853
  if @connection.threaded
1775
1854
  t = Thread.current
1776
1855
  @threads_waiting_on_confirms_continuations << t
1777
1856
 
1778
1857
  begin
1779
- outstanding_confirms = false
1780
- @unconfirmed_set_mutex.synchronize do
1781
- outstanding_confirms = !@unconfirmed_set.empty?
1858
+ while @unconfirmed_set_mutex.synchronize { !@unconfirmed_set.empty? }
1859
+ @confirms_continuations.poll(@connection.continuation_timeout)
1782
1860
  end
1783
- @confirms_continuations.poll(@connection.continuation_timeout) if outstanding_confirms
1784
1861
  ensure
1785
1862
  @threads_waiting_on_confirms_continuations.delete(t)
1786
1863
  end
@@ -1847,37 +1924,37 @@ module Bunny
1847
1924
 
1848
1925
  # @private
1849
1926
  def deregister_queue(queue)
1850
- @queues.delete(queue.name)
1927
+ @queue_mutex.synchronize { @queues.delete(queue.name) }
1851
1928
  end
1852
1929
 
1853
1930
  # @private
1854
1931
  def deregister_queue_named(name)
1855
- @queues.delete(name)
1932
+ @queue_mutex.synchronize { @queues.delete(name) }
1856
1933
  end
1857
1934
 
1858
1935
  # @private
1859
1936
  def register_queue(queue)
1860
- @queues[queue.name] = queue
1937
+ @queue_mutex.synchronize { @queues[queue.name] = queue }
1861
1938
  end
1862
1939
 
1863
1940
  # @private
1864
1941
  def find_queue(name)
1865
- @queues[name]
1942
+ @queue_mutex.synchronize { @queues[name] }
1866
1943
  end
1867
1944
 
1868
1945
  # @private
1869
1946
  def deregister_exchange(exchange)
1870
- @exchanges.delete(exchange.name)
1947
+ @exchange_mutex.synchronize { @exchanges.delete(exchange.name) }
1871
1948
  end
1872
1949
 
1873
1950
  # @private
1874
1951
  def register_exchange(exchange)
1875
- @exchanges[exchange.name] = exchange
1952
+ @exchange_mutex.synchronize { @exchanges[exchange.name] = exchange }
1876
1953
  end
1877
1954
 
1878
1955
  # @private
1879
1956
  def find_exchange(name)
1880
- @exchanges[name]
1957
+ @exchange_mutex.synchronize { @exchanges[name] }
1881
1958
  end
1882
1959
 
1883
1960
  protected
@@ -1917,7 +1994,13 @@ module Bunny
1917
1994
 
1918
1995
  # @private
1919
1996
  def raise_if_no_longer_open!
1920
- 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
1921
2004
  end
1922
2005
 
1923
2006
  # @private
@@ -1957,7 +2040,7 @@ module Bunny
1957
2040
  def guarding_against_stale_delivery_tags(tag, &block)
1958
2041
  case tag
1959
2042
  # if a fixnum was passed, execute unconditionally. MK.
1960
- when Fixnum then
2043
+ when Integer then
1961
2044
  block.call
1962
2045
  # versioned delivery tags should be checked to avoid
1963
2046
  # sending out stale (invalid) tags after channel was reopened