bunny 1.0.7 → 2.24.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 (168) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +92 -87
  3. data/lib/amq/protocol/extensions.rb +2 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +2 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +2 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
  7. data/lib/bunny/channel.rb +485 -186
  8. data/lib/bunny/channel_id_allocator.rb +8 -4
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +2 -0
  10. data/lib/bunny/concurrent/condition.rb +2 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +37 -13
  12. data/lib/bunny/concurrent/synchronized_sorted_set.rb +2 -0
  13. data/lib/bunny/consumer.rb +20 -13
  14. data/lib/bunny/consumer_tag_generator.rb +6 -2
  15. data/lib/bunny/consumer_work_pool.rb +37 -7
  16. data/lib/bunny/cruby/socket.rb +51 -22
  17. data/lib/bunny/cruby/ssl_socket.rb +68 -5
  18. data/lib/bunny/delivery_info.rb +3 -1
  19. data/lib/bunny/exceptions.rb +27 -4
  20. data/lib/bunny/exchange.rb +35 -29
  21. data/lib/bunny/framing.rb +2 -0
  22. data/lib/bunny/get_response.rb +85 -0
  23. data/lib/bunny/heartbeat_sender.rb +9 -6
  24. data/lib/bunny/message_properties.rb +2 -0
  25. data/lib/bunny/queue.rb +89 -41
  26. data/lib/bunny/reader_loop.rb +72 -28
  27. data/lib/bunny/return_info.rb +2 -0
  28. data/lib/bunny/session.rb +621 -225
  29. data/lib/bunny/socket.rb +7 -12
  30. data/lib/bunny/ssl_socket.rb +7 -12
  31. data/lib/bunny/test_kit.rb +15 -0
  32. data/lib/bunny/timeout.rb +3 -12
  33. data/lib/bunny/timestamp.rb +24 -0
  34. data/lib/bunny/transport.rb +223 -98
  35. data/lib/bunny/version.rb +2 -1
  36. data/lib/bunny/versioned_delivery_tag.rb +2 -0
  37. data/lib/bunny.rb +54 -8
  38. metadata +38 -224
  39. data/.gitignore +0 -22
  40. data/.rspec +0 -3
  41. data/.ruby-version +0 -1
  42. data/.travis.yml +0 -23
  43. data/.yardopts +0 -8
  44. data/ChangeLog.md +0 -1092
  45. data/Gemfile +0 -54
  46. data/LICENSE +0 -21
  47. data/benchmarks/basic_publish/with_128K_messages.rb +0 -35
  48. data/benchmarks/basic_publish/with_1k_messages.rb +0 -35
  49. data/benchmarks/basic_publish/with_4K_messages.rb +0 -35
  50. data/benchmarks/basic_publish/with_64K_messages.rb +0 -35
  51. data/benchmarks/channel_open.rb +0 -28
  52. data/benchmarks/mutex_and_monitor.rb +0 -42
  53. data/benchmarks/queue_declare.rb +0 -29
  54. data/benchmarks/queue_declare_and_bind.rb +0 -29
  55. data/benchmarks/queue_declare_bind_and_delete.rb +0 -29
  56. data/benchmarks/synchronized_sorted_set.rb +0 -53
  57. data/benchmarks/write_vs_write_nonblock.rb +0 -49
  58. data/bin/ci/before_build.sh +0 -31
  59. data/bunny.gemspec +0 -40
  60. data/examples/connection/authentication_failure.rb +0 -16
  61. data/examples/connection/automatic_recovery_with_basic_get.rb +0 -40
  62. data/examples/connection/automatic_recovery_with_client_named_queues.rb +0 -36
  63. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +0 -46
  64. data/examples/connection/automatic_recovery_with_server_named_queues.rb +0 -35
  65. data/examples/connection/channel_level_exception.rb +0 -35
  66. data/examples/connection/disabled_automatic_recovery.rb +0 -34
  67. data/examples/connection/heartbeat.rb +0 -17
  68. data/examples/connection/manually_reconnecting_consumer.rb +0 -23
  69. data/examples/connection/manually_reconnecting_publisher.rb +0 -28
  70. data/examples/connection/unknown_host.rb +0 -16
  71. data/examples/guides/exchanges/direct_exchange_routing.rb +0 -36
  72. data/examples/guides/exchanges/fanout_exchange_routing.rb +0 -28
  73. data/examples/guides/exchanges/headers_exchange_routing.rb +0 -31
  74. data/examples/guides/exchanges/mandatory_messages.rb +0 -30
  75. data/examples/guides/extensions/alternate_exchange.rb +0 -28
  76. data/examples/guides/extensions/basic_nack.rb +0 -33
  77. data/examples/guides/extensions/connection_blocked.rb +0 -35
  78. data/examples/guides/extensions/consumer_cancellation_notification.rb +0 -39
  79. data/examples/guides/extensions/dead_letter_exchange.rb +0 -32
  80. data/examples/guides/extensions/exchange_to_exchange_bindings.rb +0 -29
  81. data/examples/guides/extensions/per_message_ttl.rb +0 -36
  82. data/examples/guides/extensions/per_queue_message_ttl.rb +0 -36
  83. data/examples/guides/extensions/publisher_confirms.rb +0 -28
  84. data/examples/guides/extensions/queue_lease.rb +0 -26
  85. data/examples/guides/extensions/sender_selected_distribution.rb +0 -32
  86. data/examples/guides/getting_started/blabbr.rb +0 -27
  87. data/examples/guides/getting_started/hello_world.rb +0 -20
  88. data/examples/guides/getting_started/weathr.rb +0 -47
  89. data/examples/guides/queues/one_off_consumer.rb +0 -23
  90. data/examples/guides/queues/redeliveries.rb +0 -79
  91. data/lib/bunny/compatibility.rb +0 -24
  92. data/lib/bunny/concurrent/linked_continuation_queue.rb +0 -61
  93. data/lib/bunny/jruby/socket.rb +0 -40
  94. data/lib/bunny/jruby/ssl_socket.rb +0 -53
  95. data/lib/bunny/system_timer.rb +0 -20
  96. data/profiling/basic_publish/with_4K_messages.rb +0 -33
  97. data/repl +0 -3
  98. data/spec/compatibility/queue_declare_spec.rb +0 -44
  99. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
  100. data/spec/higher_level_api/integration/basic_ack_spec.rb +0 -71
  101. data/spec/higher_level_api/integration/basic_cancel_spec.rb +0 -76
  102. data/spec/higher_level_api/integration/basic_consume_spec.rb +0 -225
  103. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +0 -54
  104. data/spec/higher_level_api/integration/basic_get_spec.rb +0 -48
  105. data/spec/higher_level_api/integration/basic_nack_spec.rb +0 -79
  106. data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -89
  107. data/spec/higher_level_api/integration/basic_qos_spec.rb +0 -29
  108. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  109. data/spec/higher_level_api/integration/basic_reject_spec.rb +0 -74
  110. data/spec/higher_level_api/integration/basic_return_spec.rb +0 -33
  111. data/spec/higher_level_api/integration/channel_close_spec.rb +0 -25
  112. data/spec/higher_level_api/integration/channel_flow_spec.rb +0 -21
  113. data/spec/higher_level_api/integration/channel_open_spec.rb +0 -57
  114. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  115. data/spec/higher_level_api/integration/connection_spec.rb +0 -400
  116. data/spec/higher_level_api/integration/connection_stop_spec.rb +0 -26
  117. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  118. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +0 -128
  119. data/spec/higher_level_api/integration/dead_lettering_spec.rb +0 -52
  120. data/spec/higher_level_api/integration/exchange_bind_spec.rb +0 -31
  121. data/spec/higher_level_api/integration/exchange_declare_spec.rb +0 -204
  122. data/spec/higher_level_api/integration/exchange_delete_spec.rb +0 -105
  123. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +0 -40
  124. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +0 -28
  125. data/spec/higher_level_api/integration/heartbeat_spec.rb +0 -31
  126. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  127. data/spec/higher_level_api/integration/message_properties_access_spec.rb +0 -95
  128. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +0 -24
  129. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +0 -77
  130. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +0 -65
  131. data/spec/higher_level_api/integration/queue_bind_spec.rb +0 -109
  132. data/spec/higher_level_api/integration/queue_declare_spec.rb +0 -190
  133. data/spec/higher_level_api/integration/queue_delete_spec.rb +0 -41
  134. data/spec/higher_level_api/integration/queue_purge_spec.rb +0 -30
  135. data/spec/higher_level_api/integration/queue_unbind_spec.rb +0 -54
  136. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +0 -60
  137. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +0 -36
  138. data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -127
  139. data/spec/higher_level_api/integration/tx_commit_spec.rb +0 -21
  140. data/spec/higher_level_api/integration/tx_rollback_spec.rb +0 -21
  141. data/spec/higher_level_api/integration/with_channel_spec.rb +0 -25
  142. data/spec/issues/issue100_spec.rb +0 -42
  143. data/spec/issues/issue141_spec.rb +0 -44
  144. data/spec/issues/issue78_spec.rb +0 -75
  145. data/spec/issues/issue83_spec.rb +0 -31
  146. data/spec/issues/issue97_attachment.json +0 -1
  147. data/spec/issues/issue97_spec.rb +0 -176
  148. data/spec/lower_level_api/integration/basic_cancel_spec.rb +0 -69
  149. data/spec/lower_level_api/integration/basic_consume_spec.rb +0 -100
  150. data/spec/spec_helper.rb +0 -64
  151. data/spec/stress/channel_open_stress_spec.rb +0 -51
  152. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -28
  153. data/spec/stress/concurrent_consumers_stress_spec.rb +0 -69
  154. data/spec/stress/concurrent_publishers_stress_spec.rb +0 -57
  155. data/spec/stress/connection_open_close_spec.rb +0 -40
  156. data/spec/stress/long_running_consumer_spec.rb +0 -83
  157. data/spec/tls/cacert.pem +0 -18
  158. data/spec/tls/client_cert.pem +0 -18
  159. data/spec/tls/client_key.pem +0 -27
  160. data/spec/tls/server_cert.pem +0 -18
  161. data/spec/tls/server_key.pem +0 -27
  162. data/spec/unit/bunny_spec.rb +0 -15
  163. data/spec/unit/concurrent/atomic_fixnum_spec.rb +0 -35
  164. data/spec/unit/concurrent/condition_spec.rb +0 -82
  165. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +0 -35
  166. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +0 -73
  167. data/spec/unit/system_timer_spec.rb +0 -10
  168. data/spec/unit/version_delivery_tag_spec.rb +0 -28
data/lib/bunny/channel.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require "thread"
3
5
  require "monitor"
4
6
  require "set"
@@ -13,11 +15,7 @@ require "bunny/delivery_info"
13
15
  require "bunny/return_info"
14
16
  require "bunny/message_properties"
15
17
 
16
- if defined?(JRUBY_VERSION)
17
- require "bunny/concurrent/linked_continuation_queue"
18
- else
19
- require "bunny/concurrent/continuation_queue"
20
- end
18
+ require "bunny/concurrent/continuation_queue"
21
19
 
22
20
  module Bunny
23
21
  # ## Channels in RabbitMQ
@@ -37,11 +35,10 @@ module Bunny
37
35
  # Channels can be opened either via `Bunny::Session#create_channel` (sufficient in the majority
38
36
  # of cases) or by instantiating `Bunny::Channel` directly:
39
37
  #
40
- # @example Using {Bunny::Session#create_channel}:
41
- # conn = Bunny.new
42
- # conn.start
38
+ # conn = Bunny.new
39
+ # conn.start
43
40
  #
44
- # ch = conn.create_channel
41
+ # ch = conn.create_channel
45
42
  #
46
43
  # This will automatically allocate a channel id.
47
44
  #
@@ -51,10 +48,8 @@ module Bunny
51
48
  # closed, too. Closed channels can no longer be used. Attempts to use them will raise
52
49
  # {Bunny::ChannelAlreadyClosed}.
53
50
  #
54
- # @example
55
- #
56
- # ch = conn.create_channel
57
- # ch.close
51
+ # ch = conn.create_channel
52
+ # ch.close
58
53
  #
59
54
  # ## Higher-level API
60
55
  #
@@ -146,6 +141,10 @@ module Bunny
146
141
  attr_reader :work_pool
147
142
  # @return [Integer] Next publisher confirmations sequence index
148
143
  attr_reader :next_publish_seq_no
144
+ # @return [Integer] Offset for the confirmations sequence index.
145
+ # This will be set to the current sequence index during automatic network failure recovery
146
+ # to keep the sequence monotonic for the user and abstract the reset from the protocol
147
+ attr_reader :delivery_tag_offset
149
148
  # @return [Hash<String, Bunny::Queue>] Queue instances declared on this channel
150
149
  attr_reader :queues
151
150
  # @return [Hash<String, Bunny::Exchange>] Exchange instances declared on this channel
@@ -157,7 +156,15 @@ module Bunny
157
156
  # @return [Hash<String, Bunny::Consumer>] Consumer instances declared on this channel
158
157
  attr_reader :consumers
159
158
 
159
+ # @return [Integer] active basic.qos prefetch value
160
+ attr_reader :prefetch_count
161
+ # @return [Integer] active basic.qos prefetch global mode
162
+ attr_reader :prefetch_global
163
+
164
+ attr_reader :cancel_consumers_before_closing
165
+
160
166
  DEFAULT_CONTENT_TYPE = "application/octet-stream".freeze
167
+ SHORTSTR_LIMIT = 255
161
168
 
162
169
  # @param [Bunny::Session] connection AMQP 0.9.1 connection
163
170
  # @param [Integer] id Channel id, pass nil to make Bunny automatically allocate it
@@ -166,6 +173,17 @@ module Bunny
166
173
  @connection = connection
167
174
  @logger = connection.logger
168
175
  @id = id || @connection.next_channel_id
176
+
177
+ # channel allocator is exhausted
178
+ if @id < 0
179
+ msg = "Cannot open a channel: max number of channels on connection reached. Connection channel_max value: #{@connection.channel_max}"
180
+ @logger.error(msg)
181
+
182
+ raise msg
183
+ else
184
+ @logger.debug { "Allocated channel id: #{@id}" }
185
+ end
186
+
169
187
  @status = :opening
170
188
 
171
189
  @connection.register_channel(self)
@@ -179,6 +197,9 @@ module Bunny
179
197
  @publishing_mutex = @connection.mutex_impl.new
180
198
  @consumer_mutex = @connection.mutex_impl.new
181
199
 
200
+ @queue_mutex = @connection.mutex_impl.new
201
+ @exchange_mutex = @connection.mutex_impl.new
202
+
182
203
  @unconfirmed_set_mutex = @connection.mutex_impl.new
183
204
 
184
205
  self.reset_continuations
@@ -191,15 +212,21 @@ module Bunny
191
212
  @threads_waiting_on_basic_get_continuations = Set.new
192
213
 
193
214
  @next_publish_seq_no = 0
215
+ @delivery_tag_offset = 0
194
216
 
195
217
  @recoveries_counter = Bunny::Concurrent::AtomicFixnum.new(0)
218
+ @uncaught_exception_handler = Proc.new do |e, consumer|
219
+ @logger.error "Uncaught exception from consumer #{consumer.to_s}: #{e.inspect} @ #{e.backtrace[0]}"
220
+ end
221
+
222
+ @cancel_consumers_before_closing = false
196
223
  end
197
224
 
198
225
  attr_reader :recoveries_counter
199
226
 
200
227
  # @private
201
- def read_write_timeout
202
- @connection.read_write_timeout
228
+ def wait_on_continuations_timeout
229
+ @connection.transport_write_timeout
203
230
  end
204
231
 
205
232
  # Opens the channel and resets its internal state
@@ -223,8 +250,29 @@ module Bunny
223
250
  # {Bunny::Queue}, {Bunny::Exchange} and {Bunny::Consumer} instances.
224
251
  # @api public
225
252
  def close
253
+ # see bunny#528
254
+ raise_if_no_longer_open!
255
+
256
+ # This is a best-effort attempt to cancel all consumers before closing the channel.
257
+ # Retries are extremely unlikely to succeed, and the channel itself is about to be closed,
258
+ # so we don't bother retrying.
259
+ if self.cancel_consumers_before_closing?
260
+ # cancelling a consumer involves using the same mutex, so avoid holding the lock
261
+ keys = @consumer_mutex.synchronize { @consumers.keys }
262
+ keys.each do |ctag|
263
+ begin
264
+ self.basic_cancel(ctag)
265
+ rescue Bunny::Exception
266
+ # ignore
267
+ rescue Bunny::ClientTimeout
268
+ # ignore
269
+ end
270
+ end
271
+ end
272
+
226
273
  @connection.close_channel(self)
227
- closed!
274
+ @status = :closed
275
+ @work_pool.shutdown
228
276
  maybe_kill_consumer_work_pool!
229
277
  end
230
278
 
@@ -240,7 +288,6 @@ module Bunny
240
288
  @status == :closed
241
289
  end
242
290
 
243
-
244
291
  #
245
292
  # @group Backwards compatibility with 0.8.0
246
293
  #
@@ -267,6 +314,23 @@ module Bunny
267
314
 
268
315
  # @endgroup
269
316
 
317
+ # @group Other settings
318
+
319
+ def configure(&block)
320
+ block.call(self) if block_given?
321
+
322
+ self
323
+ end
324
+
325
+ def cancel_consumers_before_closing!
326
+ @cancel_consumers_before_closing = true
327
+ end
328
+
329
+ def cancel_consumers_before_closing?
330
+ !!@cancel_consumers_before_closing
331
+ end
332
+
333
+ # @endgroup
270
334
 
271
335
  #
272
336
  # Higher-level API, similar to amqp gem
@@ -289,7 +353,7 @@ module Bunny
289
353
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
290
354
  # @api public
291
355
  def fanout(name, opts = {})
292
- Exchange.new(self, :fanout, name, opts)
356
+ find_exchange(name) || Exchange.new(self, :fanout, name, opts)
293
357
  end
294
358
 
295
359
  # Declares a direct exchange or looks it up in the cache of previously
@@ -307,7 +371,7 @@ module Bunny
307
371
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
308
372
  # @api public
309
373
  def direct(name, opts = {})
310
- Exchange.new(self, :direct, name, opts)
374
+ find_exchange(name) || Exchange.new(self, :direct, name, opts)
311
375
  end
312
376
 
313
377
  # Declares a topic exchange or looks it up in the cache of previously
@@ -325,7 +389,7 @@ module Bunny
325
389
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
326
390
  # @api public
327
391
  def topic(name, opts = {})
328
- Exchange.new(self, :topic, name, opts)
392
+ find_exchange(name) || Exchange.new(self, :topic, name, opts)
329
393
  end
330
394
 
331
395
  # Declares a headers exchange or looks it up in the cache of previously
@@ -343,14 +407,14 @@ module Bunny
343
407
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
344
408
  # @api public
345
409
  def headers(name, opts = {})
346
- Exchange.new(self, :headers, name, opts)
410
+ find_exchange(name) || Exchange.new(self, :headers, name, opts)
347
411
  end
348
412
 
349
413
  # Provides access to the default exchange
350
414
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
351
415
  # @api public
352
416
  def default_exchange
353
- self.direct(AMQ::Protocol::EMPTY_STRING, :no_declare => true)
417
+ @default_exchange ||= Exchange.default(self)
354
418
  end
355
419
 
356
420
  # Declares a headers exchange or looks it up in the cache of previously
@@ -368,7 +432,7 @@ module Bunny
368
432
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
369
433
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
370
434
  def exchange(name, opts = {})
371
- Exchange.new(self, opts.fetch(:type, :direct), name, opts)
435
+ find_exchange(name) || Exchange.new(self, opts.fetch(:type, :direct), name, opts)
372
436
  end
373
437
 
374
438
  # @endgroup
@@ -384,34 +448,105 @@ module Bunny
384
448
  # @option opts [Boolean] :durable (false) Should this queue be durable?
385
449
  # @option opts [Boolean] :auto-delete (false) Should this queue be automatically deleted when the last consumer disconnects?
386
450
  # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
387
- # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
451
+ # @option opts [Hash] :arguments ({}) Optional arguments (x-arguments)
388
452
  #
389
453
  # @return [Bunny::Queue] Queue that was declared or looked up in the cache
390
454
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
391
455
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
392
456
  # @api public
393
457
  def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {})
458
+ throw ArgumentError.new("queue name must not be nil") if name.nil?
459
+
394
460
  q = find_queue(name) || Bunny::Queue.new(self, name, opts)
395
461
 
396
462
  register_queue(q)
397
463
  end
398
464
 
399
- # @endgroup
465
+ # Declares a new client-named quorum queue.
466
+ #
467
+ # @param [String] name Queue name. Empty (server-generated) names are not supported by this method.
468
+ # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored.
469
+ #
470
+ # @option opts [Hash] :arguments ({}) Optional arguments (x-arguments)
471
+ #
472
+ # @return [Bunny::Queue] Queue that was declared
473
+ # @see #durable_queue
474
+ # @see #queue
475
+ # @api public
476
+ def quorum_queue(name, opts = {})
477
+ throw ArgumentError.new("quorum queue name must not be nil") if name.nil?
478
+ throw ArgumentError.new("quorum queue name must not be empty (server-named QQs do not make sense)") if name.empty?
400
479
 
480
+ durable_queue(name, Bunny::Queue::Types::QUORUM, opts)
481
+ end
401
482
 
402
- # @group QoS and Flow Control
483
+ # Declares a new client-named stream (that Bunny can use as if it was a queue).
484
+ # Note that Bunny would still use AMQP 0-9-1 to perform operations on this "queue".
485
+ # To use stream-specific operations and to gain from stream protocol efficiency and partitioning,
486
+ # use a Ruby client for the RabbitMQ stream protocol.
487
+ #
488
+ # @param [String] name Stream name. Empty (server-generated) names are not supported by this method.
489
+ # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored.
490
+ #
491
+ # @option opts [Hash] :arguments ({}) Optional arguments (x-arguments)
492
+ #
493
+ #
494
+ # @return [Bunny::Queue] Queue that was declared
495
+ # @see #durable_queue
496
+ # @see #queue
497
+ # @api public
498
+ def stream(name, opts = {})
499
+ throw ArgumentError.new("stream name must not be nil") if name.nil?
500
+ throw ArgumentError.new("stream name must not be empty (server-named QQs do not make sense)") if name.empty?
501
+
502
+ durable_queue(name, Bunny::Queue::Types::STREAM, opts)
503
+ end
403
504
 
404
- # Sets how many messages will be given to consumers on this channel before they
405
- # have to acknowledge or reject one of the previously consumed messages
505
+ # Declares a new server-named queue that is automatically deleted when the
506
+ # connection is closed.
406
507
  #
407
- # @param [Integer] prefetch_count Prefetch (QoS setting) for this channel
408
- # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
409
- # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
508
+ # @param [String] name Queue name. Empty (server-generated) names are not supported by this method.
509
+ # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored.
510
+ #
511
+ # @option opts [Hash] :arguments ({}) Optional arguments (x-arguments)
512
+ #
513
+ # @return [Bunny::Queue] Queue that was declared
514
+ # @see #queue
410
515
  # @api public
411
- def prefetch(prefetch_count)
412
- self.basic_qos(prefetch_count, false)
516
+ def durable_queue(name, type = "classic", opts = {})
517
+ throw ArgumentError.new("queue name must not be nil") if name.nil?
518
+ throw ArgumentError.new("queue name must not be empty (server-named durable queues do not make sense)") if name.empty?
519
+
520
+ final_opts = opts.merge({
521
+ :type => type,
522
+ :durable => true,
523
+ # exclusive or auto-delete QQs do not make much sense
524
+ :exclusive => false,
525
+ :auto_delete => false
526
+ })
527
+ q = find_queue(name) || Bunny::Queue.new(self, name, final_opts)
528
+
529
+ register_queue(q)
413
530
  end
414
531
 
532
+ # Declares a new server-named queue that is automatically deleted when the
533
+ # connection is closed.
534
+ #
535
+ # @return [Bunny::Queue] Queue that was declared
536
+ # @see #queue
537
+ # @api public
538
+ def temporary_queue(opts = {})
539
+ temporary_queue_opts = {
540
+ :exclusive => true
541
+ }
542
+ queue("", opts.merge(temporary_queue_opts))
543
+ end
544
+
545
+ # @endgroup
546
+
547
+
548
+ # @group QoS and Flow Control
549
+
415
550
  # Flow control. When set to false, RabbitMQ will stop delivering messages on this
416
551
  # channel.
417
552
  #
@@ -444,9 +579,7 @@ module Bunny
444
579
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
445
580
  # @api public
446
581
  def reject(delivery_tag, requeue = false)
447
- guarding_against_stale_delivery_tags(delivery_tag) do
448
- basic_reject(delivery_tag.to_i, requeue)
449
- end
582
+ basic_reject(delivery_tag.to_i, requeue)
450
583
  end
451
584
 
452
585
  # Acknowledges a message. Acknowledged messages are completely removed from the queue.
@@ -457,9 +590,7 @@ module Bunny
457
590
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
458
591
  # @api public
459
592
  def ack(delivery_tag, multiple = false)
460
- guarding_against_stale_delivery_tags(delivery_tag) do
461
- basic_ack(delivery_tag.to_i, multiple)
462
- end
593
+ basic_ack(delivery_tag.to_i, multiple)
463
594
  end
464
595
  alias acknowledge ack
465
596
 
@@ -474,9 +605,7 @@ module Bunny
474
605
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
475
606
  # @api public
476
607
  def nack(delivery_tag, multiple = false, requeue = false)
477
- guarding_against_stale_delivery_tags(delivery_tag) do
478
- basic_nack(delivery_tag.to_i, multiple, requeue)
479
- end
608
+ basic_nack(delivery_tag.to_i, multiple, requeue)
480
609
  end
481
610
 
482
611
  # @endgroup
@@ -513,6 +642,7 @@ module Bunny
513
642
  # @api public
514
643
  def basic_publish(payload, exchange, routing_key, opts = {})
515
644
  raise_if_no_longer_open!
645
+ raise ArgumentError, "routing key cannot be longer than #{SHORTSTR_LIMIT} characters" if routing_key && routing_key.size > SHORTSTR_LIMIT
516
646
 
517
647
  exchange_name = if exchange.respond_to?(:name)
518
648
  exchange.name
@@ -531,8 +661,10 @@ module Bunny
531
661
  opts[:priority] ||= 0
532
662
 
533
663
  if @next_publish_seq_no > 0
534
- @unconfirmed_set.add(@next_publish_seq_no)
535
- @next_publish_seq_no += 1
664
+ @unconfirmed_set_mutex.synchronize do
665
+ @unconfirmed_set.add(@next_publish_seq_no)
666
+ @next_publish_seq_no += 1
667
+ end
536
668
  end
537
669
 
538
670
  frames = AMQ::Protocol::Basic::Publish.encode(@id,
@@ -543,7 +675,7 @@ module Bunny
543
675
  opts[:mandatory],
544
676
  false,
545
677
  @connection.frame_max)
546
- @connection.send_frameset_without_timeout(frames, self)
678
+ @connection.send_frameset(frames, self)
547
679
 
548
680
  self
549
681
  end
@@ -555,7 +687,8 @@ module Bunny
555
687
  # @param [String] queue Queue name
556
688
  # @param [Hash] opts Options
557
689
  #
558
- # @option opts [Boolean] :ack (true) Will this message be acknowledged manually?
690
+ # @option opts [Boolean] :ack (true) [DEPRECATED] Use :manual_ack instead
691
+ # @option opts [Boolean] :manual_ack (true) Will this message be acknowledged manually?
559
692
  #
560
693
  # @return [Array] A triple of delivery info, message properties and message content
561
694
  #
@@ -564,15 +697,20 @@ module Bunny
564
697
  # conn.start
565
698
  # ch = conn.create_channel
566
699
  # # here we assume the queue already exists and has messages
567
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue1", :ack => true)
700
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue1", :manual_ack => true)
568
701
  # ch.acknowledge(delivery_info.delivery_tag)
569
702
  # @see Bunny::Queue#pop
570
703
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
571
704
  # @api public
572
- def basic_get(queue, opts = {:ack => true})
705
+ def basic_get(queue, opts = {:manual_ack => true})
573
706
  raise_if_no_longer_open!
574
707
 
575
- @connection.send_frame(AMQ::Protocol::Basic::Get.encode(@id, queue, !(opts[:ack])))
708
+ unless opts[:ack].nil?
709
+ warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
710
+ opts[:manual_ack] = opts[:ack]
711
+ end
712
+
713
+ @connection.send_frame(AMQ::Protocol::Basic::Get.encode(@id, queue, !(opts[:manual_ack])))
576
714
  # this is a workaround for the edge case when basic_get is called in a tight loop
577
715
  # and network goes down we need to perform recovery. The problem is, basic_get will
578
716
  # keep blocking the thread that calls it without clear way to constantly unblock it
@@ -580,40 +718,59 @@ module Bunny
580
718
  # implementation (and even more correct and convenient ones, such as wait/notify, should
581
719
  # we implement them). So we return a triple of nils immediately which apps should be
582
720
  # able to handle anyway as "got no message, no need to act". MK.
583
- @last_basic_get_response = if @connection.open?
584
- wait_on_basic_get_continuations
585
- else
586
- [nil, nil, nil]
587
- end
721
+ last_basic_get_response = if @connection.open?
722
+ begin
723
+ wait_on_basic_get_continuations
724
+ rescue Timeout::Error => e
725
+ raise_if_continuation_resulted_in_a_channel_error!
726
+ raise e
727
+ end
728
+ else
729
+ [nil, nil, nil]
730
+ end
588
731
 
589
732
  raise_if_continuation_resulted_in_a_channel_error!
590
- @last_basic_get_response
733
+ last_basic_get_response
591
734
  end
592
735
 
736
+ # prefetch_count is of type short in the protocol. MK.
737
+ MAX_PREFETCH_COUNT = (2 ** 16) - 1
738
+
593
739
  # Controls message delivery rate using basic.qos AMQP 0.9.1 method.
594
740
  #
595
741
  # @param [Integer] prefetch_count How many messages can consumers on this channel be given at a time
596
742
  # (before they have to acknowledge or reject one of the earlier received messages)
597
- # @param [Boolean] global (false) Ignored, as it is not supported by RabbitMQ
743
+ # @param [Boolean] global
744
+ # Whether to use global mode for prefetch:
745
+ # - +false+: per-consumer
746
+ # - +true+: per-channel
747
+ # Note that the default value (+false+) hasn't actually changed, but
748
+ # previous documentation described that as meaning per-channel and
749
+ # unsupported in RabbitMQ, whereas it now actually appears to mean
750
+ # per-consumer and supported
751
+ # (https://www.rabbitmq.com/consumer-prefetch.html).
598
752
  # @return [AMQ::Protocol::Basic::QosOk] RabbitMQ response
599
753
  # @see Bunny::Channel#prefetch
600
754
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
601
755
  # @api public
602
- def basic_qos(prefetch_count, global = false)
603
- raise ArgumentError.new("prefetch count must be a positive integer, given: #{prefetch_count}") if prefetch_count < 0
756
+ def basic_qos(count, global = false)
757
+ raise ArgumentError.new("prefetch count must be a positive integer, given: #{count}") if count < 0
758
+ raise ArgumentError.new("prefetch count must be no greater than #{MAX_PREFETCH_COUNT}, given: #{count}") if count > MAX_PREFETCH_COUNT
604
759
  raise_if_no_longer_open!
605
760
 
606
- @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, prefetch_count, global))
761
+ @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
607
762
 
608
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
763
+ with_continuation_timeout do
609
764
  @last_basic_qos_ok = wait_on_continuations
610
765
  end
611
766
  raise_if_continuation_resulted_in_a_channel_error!
612
767
 
613
- @prefetch_count = prefetch_count
768
+ @prefetch_count = count
769
+ @prefetch_global = global
614
770
 
615
771
  @last_basic_qos_ok
616
772
  end
773
+ alias prefetch basic_qos
617
774
 
618
775
  # Redeliver unacknowledged messages
619
776
  #
@@ -624,7 +781,7 @@ module Bunny
624
781
  raise_if_no_longer_open!
625
782
 
626
783
  @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
627
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
784
+ with_continuation_timeout do
628
785
  @last_basic_recover_ok = wait_on_continuations
629
786
  end
630
787
  raise_if_continuation_resulted_in_a_channel_error!
@@ -654,7 +811,7 @@ module Bunny
654
811
  #
655
812
  # ch = conn.create_channel
656
813
  # q.subscribe do |delivery_info, properties, payload|
657
- # # requeue the message
814
+ # # reject the message
658
815
  # ch.basic_reject(delivery_info.delivery_tag, false)
659
816
  # end
660
817
  #
@@ -664,17 +821,19 @@ module Bunny
664
821
  #
665
822
  # ch = conn.create_channel
666
823
  # # we assume the queue exists and has messages
667
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
824
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
668
825
  # ch.basic_reject(delivery_info.delivery_tag, true)
669
826
  #
670
827
  # @see Bunny::Channel#basic_nack
671
828
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
672
829
  # @api public
673
- def basic_reject(delivery_tag, requeue)
674
- raise_if_no_longer_open!
675
- @connection.send_frame(AMQ::Protocol::Basic::Reject.encode(@id, delivery_tag, requeue))
830
+ def basic_reject(delivery_tag, requeue = false)
831
+ guarding_against_stale_delivery_tags(delivery_tag) do
832
+ raise_if_no_longer_open!
833
+ @connection.send_frame(AMQ::Protocol::Basic::Reject.encode(@id, delivery_tag, requeue))
676
834
 
677
- nil
835
+ nil
836
+ end
678
837
  end
679
838
 
680
839
  # Acknowledges a delivery (message).
@@ -690,7 +849,7 @@ module Bunny
690
849
  # ch = conn.create_channel
691
850
  # q.subscribe do |delivery_info, properties, payload|
692
851
  # # requeue the message
693
- # ch.basic_ack(delivery_info.delivery_tag)
852
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
694
853
  # end
695
854
  #
696
855
  # @example Ack a message fetched via basic.get
@@ -699,8 +858,8 @@ module Bunny
699
858
  #
700
859
  # ch = conn.create_channel
701
860
  # # we assume the queue exists and has messages
702
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
703
- # ch.basic_ack(delivery_info.delivery_tag)
861
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
862
+ # ch.basic_ack(delivery_info.delivery_tag.to_i)
704
863
  #
705
864
  # @example Ack multiple messages fetched via basic.get
706
865
  # conn = Bunny.new
@@ -708,20 +867,21 @@ module Bunny
708
867
  #
709
868
  # ch = conn.create_channel
710
869
  # # we assume the queue exists and has messages
711
- # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :ack => true)
712
- # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :ack => true)
713
- # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :ack => true)
870
+ # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
871
+ # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
872
+ # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
714
873
  # # ack all fetched messages up to payload3
715
- # ch.basic_ack(delivery_info.delivery_tag, true)
874
+ # ch.basic_ack(delivery_info.delivery_tag.to_i, true)
716
875
  #
717
876
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
718
- # @see #basic_ack_known_delivery_tag
719
877
  # @api public
720
- def basic_ack(delivery_tag, multiple)
721
- raise_if_no_longer_open!
722
- @connection.send_frame(AMQ::Protocol::Basic::Ack.encode(@id, delivery_tag, multiple))
878
+ def basic_ack(delivery_tag, multiple = false)
879
+ guarding_against_stale_delivery_tags(delivery_tag) do
880
+ raise_if_no_longer_open!
881
+ @connection.send_frame(AMQ::Protocol::Basic::Ack.encode(@id, delivery_tag, multiple))
723
882
 
724
- nil
883
+ nil
884
+ end
725
885
  end
726
886
 
727
887
  # Rejects or requeues messages just like {Bunny::Channel#basic_reject} but can do so
@@ -758,7 +918,7 @@ module Bunny
758
918
  #
759
919
  # ch = conn.create_channel
760
920
  # # we assume the queue exists and has messages
761
- # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :ack => true)
921
+ # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
762
922
  # ch.basic_nack(delivery_info.delivery_tag, false, true)
763
923
  #
764
924
  #
@@ -768,9 +928,9 @@ module Bunny
768
928
  #
769
929
  # ch = conn.create_channel
770
930
  # # we assume the queue exists and has messages
771
- # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :ack => true)
772
- # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :ack => true)
773
- # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :ack => true)
931
+ # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
932
+ # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
933
+ # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true)
774
934
  # # requeue all fetched messages up to payload3
775
935
  # ch.basic_nack(delivery_info.delivery_tag, true, true)
776
936
  #
@@ -778,13 +938,15 @@ module Bunny
778
938
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
779
939
  # @api public
780
940
  def basic_nack(delivery_tag, multiple = false, requeue = false)
781
- raise_if_no_longer_open!
782
- @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
783
- delivery_tag,
784
- multiple,
785
- requeue))
941
+ guarding_against_stale_delivery_tags(delivery_tag) do
942
+ raise_if_no_longer_open!
943
+ @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id,
944
+ delivery_tag,
945
+ multiple,
946
+ requeue))
786
947
 
787
- nil
948
+ nil
949
+ end
788
950
  end
789
951
 
790
952
  # Registers a consumer for queue. Delivered messages will be handled with the block
@@ -826,7 +988,7 @@ module Bunny
826
988
  arguments))
827
989
 
828
990
  begin
829
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
991
+ with_continuation_timeout do
830
992
  @last_basic_consume_ok = wait_on_continuations
831
993
  end
832
994
  rescue Exception => e
@@ -876,7 +1038,7 @@ module Bunny
876
1038
  consumer.arguments))
877
1039
 
878
1040
  begin
879
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1041
+ with_continuation_timeout do
880
1042
  @last_basic_consume_ok = wait_on_continuations
881
1043
  end
882
1044
  rescue Exception => e
@@ -904,18 +1066,29 @@ module Bunny
904
1066
  # it was on is auto-deleted and this consumer was the last one, the queue will be deleted.
905
1067
  #
906
1068
  # @param [String] consumer_tag Consumer tag (unique identifier) to cancel
1069
+ # @param [Hash] arguments ({}) Optional arguments
907
1070
  #
908
- # @return [AMQ::Protocol::Basic::CancelOk] RabbitMQ response
1071
+ # @option opts [Boolean] :no_wait (false) if set to true, this method won't receive a response and will
1072
+ # immediately return nil
1073
+ #
1074
+ # @return [AMQ::Protocol::Basic::CancelOk] RabbitMQ response or nil, if the no_wait option is used
909
1075
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
910
1076
  # @api public
911
- def basic_cancel(consumer_tag)
912
- @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
1077
+ def basic_cancel(consumer_tag, opts = {})
1078
+ no_wait = opts.fetch(:no_wait, false)
1079
+ @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, no_wait))
913
1080
 
914
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
915
- @last_basic_cancel_ok = wait_on_continuations
1081
+ if no_wait
1082
+ @last_basic_cancel_ok = nil
1083
+ else
1084
+ with_continuation_timeout do
1085
+ @last_basic_cancel_ok = wait_on_continuations
1086
+ end
916
1087
  end
917
1088
 
918
- maybe_kill_consumer_work_pool! unless any_consumers?
1089
+ # reduces thread usage for channels that don't have any
1090
+ # consumers
1091
+ @work_pool.shutdown(true) unless self.any_consumers?
919
1092
 
920
1093
  @last_basic_cancel_ok
921
1094
  end
@@ -933,7 +1106,8 @@ module Bunny
933
1106
 
934
1107
  # Declares a queue using queue.declare AMQP 0.9.1 method.
935
1108
  #
936
- # @param [String] name Queue name
1109
+ # @param [String] name The name of the queue or an empty string to let RabbitMQ generate a name.
1110
+ # Note that LF and CR characters will be stripped from the value.
937
1111
  # @param [Hash] opts Queue properties
938
1112
  #
939
1113
  # @option opts [Boolean] durable (false) Should information about this queue be persisted to disk so that it
@@ -944,23 +1118,37 @@ module Bunny
944
1118
  # connection is closed
945
1119
  # @option opts [Boolean] passive (false) If true, queue will be checked for existence. If it does not
946
1120
  # exist, {Bunny::NotFound} will be raised.
947
- #
1121
+ # @option opts [Hash] :arguments ({}) Optional queue arguments (x-arguments)
948
1122
  # @return [AMQ::Protocol::Queue::DeclareOk] RabbitMQ response
949
1123
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
950
1124
  # @api public
951
1125
  def queue_declare(name, opts = {})
952
1126
  raise_if_no_longer_open!
953
1127
 
954
- @connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
955
- name,
1128
+ Bunny::Queue.verify_type!(opts[:arguments]) if opts[:arguments]
1129
+
1130
+ # strip trailing new line and carriage returns
1131
+ # just like RabbitMQ does
1132
+ safe_name = name.gsub(/[\r\n]/, "")
1133
+ @pending_queue_declare_name = safe_name
1134
+ @connection.send_frame(
1135
+ AMQ::Protocol::Queue::Declare.encode(@id,
1136
+ @pending_queue_declare_name,
956
1137
  opts.fetch(:passive, false),
957
1138
  opts.fetch(:durable, false),
958
1139
  opts.fetch(:exclusive, false),
959
1140
  opts.fetch(:auto_delete, false),
960
1141
  false,
961
1142
  opts[:arguments]))
962
- @last_queue_declare_ok = wait_on_continuations
963
1143
 
1144
+ begin
1145
+ with_continuation_timeout do
1146
+ @last_queue_declare_ok = wait_on_continuations
1147
+ end
1148
+ ensure
1149
+ # clear pending continuation context if it belongs to us
1150
+ @pending_queue_declare_name = nil if @pending_queue_declare_name == safe_name
1151
+ end
964
1152
  raise_if_continuation_resulted_in_a_channel_error!
965
1153
 
966
1154
  @last_queue_declare_ok
@@ -985,7 +1173,7 @@ module Bunny
985
1173
  opts[:if_unused],
986
1174
  opts[:if_empty],
987
1175
  false))
988
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1176
+ with_continuation_timeout do
989
1177
  @last_queue_delete_ok = wait_on_continuations
990
1178
  end
991
1179
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1005,7 +1193,7 @@ module Bunny
1005
1193
 
1006
1194
  @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
1007
1195
 
1008
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1196
+ with_continuation_timeout do
1009
1197
  @last_queue_purge_ok = wait_on_continuations
1010
1198
  end
1011
1199
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1038,10 +1226,10 @@ module Bunny
1038
1226
  @connection.send_frame(AMQ::Protocol::Queue::Bind.encode(@id,
1039
1227
  name,
1040
1228
  exchange_name,
1041
- opts[:routing_key],
1229
+ (opts[:routing_key] || opts[:key]),
1042
1230
  false,
1043
1231
  opts[:arguments]))
1044
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1232
+ with_continuation_timeout do
1045
1233
  @last_queue_bind_ok = wait_on_continuations
1046
1234
  end
1047
1235
 
@@ -1076,7 +1264,7 @@ module Bunny
1076
1264
  exchange_name,
1077
1265
  opts[:routing_key],
1078
1266
  opts[:arguments]))
1079
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1267
+ with_continuation_timeout do
1080
1268
  @last_queue_unbind_ok = wait_on_continuations
1081
1269
  end
1082
1270
 
@@ -1089,33 +1277,38 @@ module Bunny
1089
1277
 
1090
1278
  # @group Exchange operations (exchange.*)
1091
1279
 
1092
- # Declares a echange using echange.declare AMQP 0.9.1 method.
1280
+ # Declares a exchange using exchange.declare AMQP 0.9.1 method.
1093
1281
  #
1094
- # @param [String] name Exchange name
1282
+ # @param [String] name The name of the exchange. Note that LF and CR characters
1283
+ # will be stripped from the value.
1284
+ # @param [String,Symbol] type Exchange type, e.g. :fanout or :topic
1095
1285
  # @param [Hash] opts Exchange properties
1096
1286
  #
1097
- # @option opts [Boolean] durable (false) Should information about this echange be persisted to disk so that it
1287
+ # @option opts [Boolean] durable (false) Should information about this exchange be persisted to disk so that it
1098
1288
  # can survive broker restarts? Typically set to true for long-lived exchanges.
1099
- # @option opts [Boolean] auto_delete (false) Should this echange be deleted when it is no longer used?
1289
+ # @option opts [Boolean] auto_delete (false) Should this exchange be deleted when it is no longer used?
1100
1290
  # @option opts [Boolean] passive (false) If true, exchange will be checked for existence. If it does not
1101
1291
  # exist, {Bunny::NotFound} will be raised.
1102
1292
  #
1103
1293
  # @return [AMQ::Protocol::Exchange::DeclareOk] RabbitMQ response
1104
- # @see http://rubybunny.info/articles/echanges.html Exchanges and Publishing guide
1294
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1105
1295
  # @api public
1106
1296
  def exchange_declare(name, type, opts = {})
1107
1297
  raise_if_no_longer_open!
1108
1298
 
1299
+ # strip trailing new line and carriage returns
1300
+ # just like RabbitMQ does
1301
+ safe_name = name.gsub(/[\r\n]/, "")
1109
1302
  @connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
1110
- name,
1303
+ safe_name,
1111
1304
  type.to_s,
1112
1305
  opts.fetch(:passive, false),
1113
1306
  opts.fetch(:durable, false),
1114
1307
  opts.fetch(:auto_delete, false),
1115
- false,
1116
- false,
1308
+ opts.fetch(:internal, false),
1309
+ opts.fetch(:no_wait, false),
1117
1310
  opts[:arguments]))
1118
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1311
+ with_continuation_timeout do
1119
1312
  @last_exchange_declare_ok = wait_on_continuations
1120
1313
  end
1121
1314
 
@@ -1140,7 +1333,7 @@ module Bunny
1140
1333
  name,
1141
1334
  opts[:if_unused],
1142
1335
  false))
1143
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1336
+ with_continuation_timeout do
1144
1337
  @last_exchange_delete_ok = wait_on_continuations
1145
1338
  end
1146
1339
 
@@ -1184,7 +1377,7 @@ module Bunny
1184
1377
  opts[:routing_key],
1185
1378
  false,
1186
1379
  opts[:arguments]))
1187
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1380
+ with_continuation_timeout do
1188
1381
  @last_exchange_bind_ok = wait_on_continuations
1189
1382
  end
1190
1383
 
@@ -1228,7 +1421,7 @@ module Bunny
1228
1421
  opts[:routing_key],
1229
1422
  false,
1230
1423
  opts[:arguments]))
1231
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1424
+ with_continuation_timeout do
1232
1425
  @last_exchange_unbind_ok = wait_on_continuations
1233
1426
  end
1234
1427
 
@@ -1256,7 +1449,7 @@ module Bunny
1256
1449
  raise_if_no_longer_open!
1257
1450
 
1258
1451
  @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
1259
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1452
+ with_continuation_timeout do
1260
1453
  @last_channel_flow_ok = wait_on_continuations
1261
1454
  end
1262
1455
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1277,10 +1470,11 @@ module Bunny
1277
1470
  raise_if_no_longer_open!
1278
1471
 
1279
1472
  @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
1280
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1473
+ with_continuation_timeout do
1281
1474
  @last_tx_select_ok = wait_on_continuations
1282
1475
  end
1283
1476
  raise_if_continuation_resulted_in_a_channel_error!
1477
+ @tx_mode = true
1284
1478
 
1285
1479
  @last_tx_select_ok
1286
1480
  end
@@ -1292,7 +1486,7 @@ module Bunny
1292
1486
  raise_if_no_longer_open!
1293
1487
 
1294
1488
  @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
1295
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1489
+ with_continuation_timeout do
1296
1490
  @last_tx_commit_ok = wait_on_continuations
1297
1491
  end
1298
1492
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1307,7 +1501,7 @@ module Bunny
1307
1501
  raise_if_no_longer_open!
1308
1502
 
1309
1503
  @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
1310
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1504
+ with_continuation_timeout do
1311
1505
  @last_tx_rollback_ok = wait_on_continuations
1312
1506
  end
1313
1507
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1315,6 +1509,11 @@ module Bunny
1315
1509
  @last_tx_rollback_ok
1316
1510
  end
1317
1511
 
1512
+ # @return [Boolean] true if this channel has transactions enabled
1513
+ def using_tx?
1514
+ !!@tx_mode
1515
+ end
1516
+
1318
1517
  # @endgroup
1319
1518
 
1320
1519
 
@@ -1326,6 +1525,7 @@ module Bunny
1326
1525
  def using_publisher_confirmations?
1327
1526
  @next_publish_seq_no > 0
1328
1527
  end
1528
+ alias using_publisher_confirms? using_publisher_confirmations?
1329
1529
 
1330
1530
  # Enables publisher confirms for the channel.
1331
1531
  # @return [AMQ::Protocol::Confirm::SelectOk] RabbitMQ response
@@ -1334,7 +1534,7 @@ module Bunny
1334
1534
  # @see #nacked_set
1335
1535
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1336
1536
  # @api public
1337
- def confirm_select(callback=nil)
1537
+ def confirm_select(callback = nil)
1338
1538
  raise_if_no_longer_open!
1339
1539
 
1340
1540
  if @next_publish_seq_no == 0
@@ -1342,12 +1542,13 @@ module Bunny
1342
1542
  @unconfirmed_set = Set.new
1343
1543
  @nacked_set = Set.new
1344
1544
  @next_publish_seq_no = 1
1545
+ @only_acks_received = true
1345
1546
  end
1346
1547
 
1347
1548
  @confirms_callback = callback
1348
1549
 
1349
1550
  @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
1350
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1551
+ with_continuation_timeout do
1351
1552
  @last_confirm_select_ok = wait_on_continuations
1352
1553
  end
1353
1554
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1355,19 +1556,18 @@ module Bunny
1355
1556
  end
1356
1557
 
1357
1558
  # Blocks calling thread until confirms are received for all
1358
- # currently unacknowledged published messages.
1559
+ # currently unacknowledged published messages. Returns immediately
1560
+ # if there are no outstanding confirms.
1359
1561
  #
1360
- # @return [Boolean] true if all messages were acknowledged positively, false otherwise
1562
+ # @return [Boolean] true if all messages were acknowledged positively since the last time this method was called, false otherwise
1361
1563
  # @see #confirm_select
1362
1564
  # @see #unconfirmed_set
1363
1565
  # @see #nacked_set
1364
1566
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
1365
1567
  # @api public
1366
1568
  def wait_for_confirms
1367
- @only_acks_received = true
1368
1569
  wait_on_confirms_continuations
1369
-
1370
- @only_acks_received
1570
+ read_and_reset_only_acks_received
1371
1571
  end
1372
1572
 
1373
1573
  # @endgroup
@@ -1386,7 +1586,8 @@ module Bunny
1386
1586
  # @return [String] Unique string.
1387
1587
  # @api plugin
1388
1588
  def generate_consumer_tag(name = "bunny")
1389
- "#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
1589
+ t = Bunny::Timestamp.now
1590
+ "#{name}-#{t.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
1390
1591
  end
1391
1592
 
1392
1593
  # @endgroup
@@ -1404,6 +1605,13 @@ module Bunny
1404
1605
  @on_error = block
1405
1606
  end
1406
1607
 
1608
+ # Defines a handler for uncaught exceptions in consumers
1609
+ # (e.g. delivered message handlers).
1610
+ #
1611
+ # @api public
1612
+ def on_uncaught_exception(&block)
1613
+ @uncaught_exception_handler = block
1614
+ end
1407
1615
 
1408
1616
  #
1409
1617
  # Recovery
@@ -1416,10 +1624,12 @@ module Bunny
1416
1624
  #
1417
1625
  # @api plugin
1418
1626
  def recover_from_network_failure
1419
- @logger.debug "Recovering channel #{@id} after network failure"
1627
+ @logger.debug { "Recovering channel #{@id} after network failure" }
1420
1628
  release_all_continuations
1421
1629
 
1422
1630
  recover_prefetch_setting
1631
+ recover_confirm_mode
1632
+ recover_tx_mode
1423
1633
  recover_exchanges
1424
1634
  # this includes recovering bindings
1425
1635
  recover_queues
@@ -1432,7 +1642,30 @@ module Bunny
1432
1642
  #
1433
1643
  # @api plugin
1434
1644
  def recover_prefetch_setting
1435
- basic_qos(@prefetch_count) if @prefetch_count
1645
+ basic_qos(@prefetch_count, @prefetch_global) if @prefetch_count
1646
+ end
1647
+
1648
+ # Recovers publisher confirms mode. Used by the Automatic Network Failure
1649
+ # Recovery feature.
1650
+ # Set the offset to the previous publish sequence index as the protocol will reset the index to after recovery.
1651
+ #
1652
+ # @api plugin
1653
+ def recover_confirm_mode
1654
+ if using_publisher_confirmations?
1655
+ @unconfirmed_set_mutex.synchronize do
1656
+ @unconfirmed_set.clear
1657
+ @delivery_tag_offset = @next_publish_seq_no - 1
1658
+ end
1659
+ confirm_select(@confirms_callback)
1660
+ end
1661
+ end
1662
+
1663
+ # Recovers transaction mode. Used by the Automatic Network Failure
1664
+ # Recovery feature.
1665
+ #
1666
+ # @api plugin
1667
+ def recover_tx_mode
1668
+ tx_select if @tx_mode
1436
1669
  end
1437
1670
 
1438
1671
  # Recovers exchanges. Used by the Automatic Network Failure
@@ -1440,7 +1673,7 @@ module Bunny
1440
1673
  #
1441
1674
  # @api plugin
1442
1675
  def recover_exchanges
1443
- @exchanges.values.dup.each do |x|
1676
+ @exchange_mutex.synchronize { @exchanges.values }.each do |x|
1444
1677
  x.recover_from_network_failure
1445
1678
  end
1446
1679
  end
@@ -1450,8 +1683,8 @@ module Bunny
1450
1683
  #
1451
1684
  # @api plugin
1452
1685
  def recover_queues
1453
- @queues.values.dup.each do |q|
1454
- @logger.debug "Recovering queue #{q.name}"
1686
+ @queue_mutex.synchronize { @queues.values }.each do |q|
1687
+ @logger.debug { "Recovering queue #{q.name}" }
1455
1688
  q.recover_from_network_failure
1456
1689
  end
1457
1690
  end
@@ -1462,10 +1695,11 @@ module Bunny
1462
1695
  # @api plugin
1463
1696
  def recover_consumers
1464
1697
  unless @consumers.empty?
1465
- @work_pool = ConsumerWorkPool.new(@work_pool.size)
1698
+ @work_pool = ConsumerWorkPool.new(@work_pool.size, @work_pool.abort_on_exception)
1466
1699
  @work_pool.start
1467
1700
  end
1468
- @consumers.values.dup.each do |c|
1701
+
1702
+ @consumer_mutex.synchronize { @consumers.values }.each do |c|
1469
1703
  c.recover_from_network_failure
1470
1704
  end
1471
1705
  end
@@ -1475,12 +1709,26 @@ module Bunny
1475
1709
  @recoveries_counter.increment
1476
1710
  end
1477
1711
 
1712
+ # @api public
1713
+ def recover_cancelled_consumers!
1714
+ @recover_cancelled_consumers = true
1715
+ end
1716
+
1717
+ # @api public
1718
+ def recovers_cancelled_consumers?
1719
+ !!@recover_cancelled_consumers
1720
+ end
1721
+
1478
1722
  # @endgroup
1479
1723
 
1480
1724
 
1481
1725
  # @return [String] Brief human-readable representation of the channel
1482
1726
  def to_s
1483
- "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}>"
1727
+ "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s} @open=#{open?}>"
1728
+ end
1729
+
1730
+ def inspect
1731
+ to_s
1484
1732
  end
1485
1733
 
1486
1734
 
@@ -1488,6 +1736,11 @@ module Bunny
1488
1736
  # Implementation
1489
1737
  #
1490
1738
 
1739
+ # @private
1740
+ def with_continuation_timeout(&block)
1741
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout, &block)
1742
+ end
1743
+
1491
1744
  # @private
1492
1745
  def register_consumer(consumer_tag, consumer)
1493
1746
  @consumer_mutex.synchronize do
@@ -1511,12 +1764,33 @@ module Bunny
1511
1764
  end
1512
1765
  end
1513
1766
 
1767
+ # @private
1768
+ def pending_server_named_queue_declaration?
1769
+ @pending_queue_declare_name && @pending_queue_declare_name.empty?
1770
+ end
1771
+
1772
+ # @private
1773
+ def can_accept_queue_declare_ok?(method)
1774
+ @pending_queue_declare_name == method.queue ||
1775
+ pending_server_named_queue_declaration?
1776
+ end
1777
+
1514
1778
  # @private
1515
1779
  def handle_method(method)
1516
- @logger.debug "Channel#handle_frame on channel #{@id}: #{method.inspect}"
1780
+ @logger.debug { "Channel#handle_frame on channel #{@id}: #{method.inspect}" }
1517
1781
  case method
1518
1782
  when AMQ::Protocol::Queue::DeclareOk then
1519
- @continuations.push(method)
1783
+ # safeguard against late arrivals of responses and
1784
+ # so on, see ruby-amqp/bunny#558
1785
+ if can_accept_queue_declare_ok?(method)
1786
+ @continuations.push(method)
1787
+ else
1788
+ if !pending_server_named_queue_declaration?
1789
+ # this response is for an outdated/overwritten
1790
+ # queue.declare, drop it
1791
+ @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 concurrent channel use or a timeout, ignoring it"
1792
+ end
1793
+ end
1520
1794
  when AMQ::Protocol::Queue::DeleteOk then
1521
1795
  @continuations.push(method)
1522
1796
  when AMQ::Protocol::Queue::PurgeOk then
@@ -1545,10 +1819,18 @@ module Bunny
1545
1819
  if consumer = @consumers[method.consumer_tag]
1546
1820
  @work_pool.submit do
1547
1821
  begin
1548
- @consumers.delete(method.consumer_tag)
1549
- consumer.handle_cancellation(method)
1822
+ if recovers_cancelled_consumers?
1823
+ consumer.handle_cancellation(method)
1824
+ @logger.info "Automatically recovering cancelled consumer #{consumer.consumer_tag} on queue #{consumer.queue_name}"
1825
+
1826
+ consume_with(consumer)
1827
+ else
1828
+ @consumers.delete(method.consumer_tag)
1829
+ consumer.handle_cancellation(method)
1830
+ end
1550
1831
  rescue Exception => e
1551
1832
  @logger.error "Got exception when notifying consumer #{method.consumer_tag} about cancellation!"
1833
+ @uncaught_exception_handler.call(e, consumer) if @uncaught_exception_handler
1552
1834
  end
1553
1835
  end
1554
1836
  else
@@ -1588,7 +1870,7 @@ module Bunny
1588
1870
 
1589
1871
  # @private
1590
1872
  def channel_level_exception_after_operation_that_has_no_response?(method)
1591
- method.reply_code == 406 && method.reply_text =~ /unknown delivery tag/
1873
+ method.reply_code == 406 && (method.reply_text =~ /unknown delivery tag/ || method.reply_text =~ /delivery acknowledgement on channel \d+ timed out/)
1592
1874
  end
1593
1875
 
1594
1876
  # @private
@@ -1607,7 +1889,11 @@ module Bunny
1607
1889
  consumer = @consumers[basic_deliver.consumer_tag]
1608
1890
  if consumer
1609
1891
  @work_pool.submit do
1610
- consumer.call(DeliveryInfo.new(basic_deliver, consumer, self), MessageProperties.new(properties), content)
1892
+ begin
1893
+ consumer.call(DeliveryInfo.new(basic_deliver, consumer, self), MessageProperties.new(properties), content)
1894
+ rescue StandardError => e
1895
+ @uncaught_exception_handler.call(e, consumer) if @uncaught_exception_handler
1896
+ end
1611
1897
  end
1612
1898
  else
1613
1899
  @logger.warn "No consumer for tag #{basic_deliver.consumer_tag} on channel #{@id}!"
@@ -1625,29 +1911,29 @@ module Bunny
1625
1911
  end
1626
1912
  end
1627
1913
 
1914
+ # Handle delivery tag offset calculations to keep the the delivery tag monotonic after a reset
1915
+ # due to automatic network failure recovery. @unconfirmed_set contains indices already offsetted.
1628
1916
  # @private
1629
- def handle_ack_or_nack(delivery_tag, multiple, nack)
1630
- if nack
1631
- cloned_set = @unconfirmed_set.clone
1632
- if multiple
1633
- cloned_set.keep_if { |i| i <= delivery_tag }
1634
- @nacked_set.merge(cloned_set)
1635
- else
1636
- @nacked_set.add(delivery_tag)
1917
+ def handle_ack_or_nack(delivery_tag_before_offset, multiple, nack)
1918
+ @unconfirmed_set_mutex.synchronize do
1919
+ delivery_tag = delivery_tag_before_offset + @delivery_tag_offset
1920
+ confirmed_range_start = multiple ? @unconfirmed_set.min : delivery_tag
1921
+ confirmed_range_end = delivery_tag
1922
+ confirmed_range = (confirmed_range_start..confirmed_range_end)
1923
+
1924
+ if nack
1925
+ @nacked_set.merge(@unconfirmed_set & confirmed_range)
1637
1926
  end
1638
- end
1639
1927
 
1640
- if multiple
1641
- @unconfirmed_set.delete_if { |i| i <= delivery_tag }
1642
- else
1643
- @unconfirmed_set.delete(delivery_tag)
1644
- end
1928
+ @unconfirmed_set.subtract(confirmed_range)
1645
1929
 
1646
- @unconfirmed_set_mutex.synchronize do
1647
1930
  @only_acks_received = (@only_acks_received && !nack)
1648
1931
 
1649
1932
  @confirms_continuations.push(true) if @unconfirmed_set.empty?
1650
- @confirms_callback.call(delivery_tag, multiple, nack) if @confirms_callback
1933
+
1934
+ if @confirms_callback
1935
+ confirmed_range.each { |tag| @confirms_callback.call(tag, false, nack) }
1936
+ end
1651
1937
  end
1652
1938
  end
1653
1939
 
@@ -1689,22 +1975,37 @@ module Bunny
1689
1975
 
1690
1976
  # @private
1691
1977
  def wait_on_confirms_continuations
1978
+ raise_if_no_longer_open!
1979
+
1692
1980
  if @connection.threaded
1693
1981
  t = Thread.current
1694
1982
  @threads_waiting_on_confirms_continuations << t
1695
1983
 
1696
1984
  begin
1697
- @confirms_continuations.poll(@connection.continuation_timeout)
1985
+ while @unconfirmed_set_mutex.synchronize { !@unconfirmed_set.empty? }
1986
+ @confirms_continuations.poll(@connection.continuation_timeout)
1987
+ end
1698
1988
  ensure
1699
1989
  @threads_waiting_on_confirms_continuations.delete(t)
1700
1990
  end
1701
1991
  else
1702
- connection.reader_loop.run_once until @confirms_continuations.length > 0
1992
+ unless @unconfirmed_set.empty?
1993
+ connection.reader_loop.run_once until @confirms_continuations.length > 0
1994
+ @confirms_continuations.pop
1995
+ end
1996
+ end
1997
+ end
1703
1998
 
1704
- @confirms_continuations.pop
1999
+ # @private
2000
+ def read_and_reset_only_acks_received
2001
+ @unconfirmed_set_mutex.synchronize do
2002
+ result = @only_acks_received
2003
+ @only_acks_received = true
2004
+ result
1705
2005
  end
1706
2006
  end
1707
2007
 
2008
+
1708
2009
  # Releases all continuations. Used by automatic network recovery.
1709
2010
  # @private
1710
2011
  def release_all_continuations
@@ -1750,37 +2051,37 @@ module Bunny
1750
2051
 
1751
2052
  # @private
1752
2053
  def deregister_queue(queue)
1753
- @queues.delete(queue.name)
2054
+ @queue_mutex.synchronize { @queues.delete(queue.name) }
1754
2055
  end
1755
2056
 
1756
2057
  # @private
1757
2058
  def deregister_queue_named(name)
1758
- @queues.delete(name)
2059
+ @queue_mutex.synchronize { @queues.delete(name) }
1759
2060
  end
1760
2061
 
1761
2062
  # @private
1762
2063
  def register_queue(queue)
1763
- @queues[queue.name] = queue
2064
+ @queue_mutex.synchronize { @queues[queue.name] = queue }
1764
2065
  end
1765
2066
 
1766
2067
  # @private
1767
2068
  def find_queue(name)
1768
- @queues[name]
2069
+ @queue_mutex.synchronize { @queues[name] }
1769
2070
  end
1770
2071
 
1771
2072
  # @private
1772
2073
  def deregister_exchange(exchange)
1773
- @exchanges.delete(exchange.name)
2074
+ @exchange_mutex.synchronize { @exchanges.delete(exchange.name) }
1774
2075
  end
1775
2076
 
1776
2077
  # @private
1777
2078
  def register_exchange(exchange)
1778
- @exchanges[exchange.name] = exchange
2079
+ @exchange_mutex.synchronize { @exchanges[exchange.name] = exchange }
1779
2080
  end
1780
2081
 
1781
2082
  # @private
1782
2083
  def find_exchange(name)
1783
- @exchanges[name]
2084
+ @exchange_mutex.synchronize { @exchanges[name] }
1784
2085
  end
1785
2086
 
1786
2087
  protected
@@ -1820,7 +2121,13 @@ module Bunny
1820
2121
 
1821
2122
  # @private
1822
2123
  def raise_if_no_longer_open!
1823
- raise ChannelAlreadyClosed.new("cannot use a channel that was already closed! Channel id: #{@id}", self) if closed?
2124
+ if closed?
2125
+ if @last_channel_error
2126
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}, closed due to a server-reported channel error: #{@last_channel_error.message}", self)
2127
+ else
2128
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}", self)
2129
+ end
2130
+ end
1824
2131
  end
1825
2132
 
1826
2133
  # @private
@@ -1843,24 +2150,16 @@ module Bunny
1843
2150
  @basic_get_continuations = new_continuation
1844
2151
  end
1845
2152
 
1846
-
1847
- if defined?(JRUBY_VERSION)
1848
- # @private
1849
- def new_continuation
1850
- Concurrent::LinkedContinuationQueue.new
1851
- end
1852
- else
1853
- # @private
1854
- def new_continuation
1855
- Concurrent::ContinuationQueue.new
1856
- end
1857
- end # if defined?
2153
+ # @private
2154
+ def new_continuation
2155
+ Concurrent::ContinuationQueue.new
2156
+ end
1858
2157
 
1859
2158
  # @private
1860
2159
  def guarding_against_stale_delivery_tags(tag, &block)
1861
2160
  case tag
1862
2161
  # if a fixnum was passed, execute unconditionally. MK.
1863
- when Fixnum then
2162
+ when Integer then
1864
2163
  block.call
1865
2164
  # versioned delivery tags should be checked to avoid
1866
2165
  # sending out stale (invalid) tags after channel was reopened