bunny 2.19.0 → 2.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -32
  3. data/lib/bunny/channel.rb +88 -12
  4. data/lib/bunny/consumer.rb +2 -2
  5. data/lib/bunny/consumer_work_pool.rb +1 -1
  6. data/lib/bunny/delivery_info.rb +1 -1
  7. data/lib/bunny/queue.rb +36 -2
  8. data/lib/bunny/session.rb +39 -16
  9. data/lib/bunny/transport.rb +37 -1
  10. data/lib/bunny/version.rb +1 -1
  11. data/lib/bunny.rb +45 -4
  12. metadata +6 -146
  13. data/spec/config/enabled_plugins +0 -1
  14. data/spec/config/rabbitmq.conf +0 -13
  15. data/spec/higher_level_api/integration/basic_ack_spec.rb +0 -230
  16. data/spec/higher_level_api/integration/basic_cancel_spec.rb +0 -142
  17. data/spec/higher_level_api/integration/basic_consume_spec.rb +0 -357
  18. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +0 -54
  19. data/spec/higher_level_api/integration/basic_get_spec.rb +0 -80
  20. data/spec/higher_level_api/integration/basic_nack_spec.rb +0 -82
  21. data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -74
  22. data/spec/higher_level_api/integration/basic_qos_spec.rb +0 -57
  23. data/spec/higher_level_api/integration/basic_reject_spec.rb +0 -152
  24. data/spec/higher_level_api/integration/basic_return_spec.rb +0 -33
  25. data/spec/higher_level_api/integration/channel_close_spec.rb +0 -66
  26. data/spec/higher_level_api/integration/channel_open_spec.rb +0 -57
  27. data/spec/higher_level_api/integration/connection_recovery_spec.rb +0 -483
  28. data/spec/higher_level_api/integration/connection_spec.rb +0 -589
  29. data/spec/higher_level_api/integration/connection_stop_spec.rb +0 -83
  30. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +0 -128
  31. data/spec/higher_level_api/integration/dead_lettering_spec.rb +0 -75
  32. data/spec/higher_level_api/integration/exchange_bind_spec.rb +0 -31
  33. data/spec/higher_level_api/integration/exchange_declare_spec.rb +0 -237
  34. data/spec/higher_level_api/integration/exchange_delete_spec.rb +0 -105
  35. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +0 -40
  36. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +0 -28
  37. data/spec/higher_level_api/integration/heartbeat_spec.rb +0 -49
  38. data/spec/higher_level_api/integration/message_properties_access_spec.rb +0 -95
  39. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +0 -24
  40. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +0 -191
  41. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +0 -87
  42. data/spec/higher_level_api/integration/queue_bind_spec.rb +0 -109
  43. data/spec/higher_level_api/integration/queue_declare_spec.rb +0 -285
  44. data/spec/higher_level_api/integration/queue_delete_spec.rb +0 -41
  45. data/spec/higher_level_api/integration/queue_purge_spec.rb +0 -30
  46. data/spec/higher_level_api/integration/queue_unbind_spec.rb +0 -54
  47. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +0 -60
  48. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +0 -36
  49. data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -255
  50. data/spec/higher_level_api/integration/toxiproxy_spec.rb +0 -76
  51. data/spec/higher_level_api/integration/tx_commit_spec.rb +0 -21
  52. data/spec/higher_level_api/integration/tx_rollback_spec.rb +0 -21
  53. data/spec/higher_level_api/integration/with_channel_spec.rb +0 -25
  54. data/spec/issues/issue100_spec.rb +0 -42
  55. data/spec/issues/issue141_spec.rb +0 -43
  56. data/spec/issues/issue202_spec.rb +0 -15
  57. data/spec/issues/issue224_spec.rb +0 -40
  58. data/spec/issues/issue465_spec.rb +0 -32
  59. data/spec/issues/issue549_spec.rb +0 -30
  60. data/spec/issues/issue609_spec.rb +0 -84
  61. data/spec/issues/issue78_spec.rb +0 -72
  62. data/spec/issues/issue83_spec.rb +0 -30
  63. data/spec/issues/issue97_attachment.json +0 -1
  64. data/spec/issues/issue97_spec.rb +0 -175
  65. data/spec/lower_level_api/integration/basic_cancel_spec.rb +0 -83
  66. data/spec/lower_level_api/integration/basic_consume_spec.rb +0 -99
  67. data/spec/spec_helper.rb +0 -47
  68. data/spec/stress/channel_close_stress_spec.rb +0 -64
  69. data/spec/stress/channel_open_stress_spec.rb +0 -84
  70. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -28
  71. data/spec/stress/concurrent_consumers_stress_spec.rb +0 -71
  72. data/spec/stress/concurrent_publishers_stress_spec.rb +0 -54
  73. data/spec/stress/connection_open_close_spec.rb +0 -52
  74. data/spec/stress/merry_go_round_spec.rb +0 -105
  75. data/spec/toxiproxy_helper.rb +0 -28
  76. data/spec/unit/bunny_spec.rb +0 -15
  77. data/spec/unit/concurrent/atomic_fixnum_spec.rb +0 -35
  78. data/spec/unit/concurrent/condition_spec.rb +0 -82
  79. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +0 -35
  80. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +0 -73
  81. data/spec/unit/exchange_recovery_spec.rb +0 -39
  82. data/spec/unit/version_delivery_tag_spec.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6de535f1d386dbf46fd89a733c1c44915280af55c3cc3cc56467abe0ead9c24
4
- data.tar.gz: 6048aea84078fb8995718ed774d9a3956dc91645b24c09382cd54283b444aabe
3
+ metadata.gz: 2edd88411f92dbdbb1d06b334006e446d5b3e3fb165f3baae3745bd066cf9651
4
+ data.tar.gz: 437032f606bb6f77fee02e65706e615f8a52c0e05467bbe257f1796fcedd624c
5
5
  SHA512:
6
- metadata.gz: b3575710f81dabf6c116c92c4ac1807a29f0c0bcd1bd5fb35ae1fd9dd468df193efac49d0ad5e77ef04ecc53e97b9b4e13e70f4207972ba0c2335c28afcd6c9b
7
- data.tar.gz: 8b90dd4da7dab28b529e78cf1282656ca1904432b1017ee5574545c1972315f975d175c718481372604b61796e9ae56b293a342dcba800fa0929e4b80cd7a3c4
6
+ metadata.gz: 698398dc76ca4e3dd11e3c5dc6b078998f23895bd3d975b01e1329c08c8ff0835cb9691d0108f0f6e13aaa769b870b258ff5e1de1958321be4804ca99b30a0be
7
+ data.tar.gz: 2a1fdcc61d683a991d07f601a986c5bc04d40cdd7155a1833dc7dba46be014c23d36612c505531b2f2adfe08ed057d6a493cbf5df368318205ffaf88659a88fb
data/README.md CHANGED
@@ -7,7 +7,7 @@ have any heavyweight dependencies.
7
7
 
8
8
  ## I Know What RabbitMQ and Bunny are, How Do I Get Started?
9
9
 
10
- [Right here](http://rubybunny.info/articles/getting_started.html)!
10
+ [Right here](https://www.rabbitmq.com/getstarted.html)!
11
11
 
12
12
 
13
13
  ## What is Bunny Good For?
@@ -47,7 +47,13 @@ Specific examples:
47
47
 
48
48
  Modern Bunny versions support
49
49
 
50
- * CRuby 2.5 through 3.0 (inclusive)
50
+ * CRuby 2.6 through 3.1 (inclusive)
51
+ * [TruffleRuby](https://www.graalvm.org/ruby/)
52
+
53
+ For environments that use TLS, Bunny expects Ruby installations to use a recent enough OpenSSL version that
54
+ **includes support for TLS 1.3**.
55
+
56
+ ### JRuby
51
57
 
52
58
  Bunny works sufficiently well on JRuby but there are known
53
59
  JRuby bugs in versions prior to JRuby 9000 that cause high CPU burn. JRuby users should
@@ -58,8 +64,7 @@ Bunny `1.7.x` was the last version to support CRuby 1.9.3 and 1.8.7
58
64
 
59
65
  ## Supported RabbitMQ Versions
60
66
 
61
- Bunny `1.5.0` and later versions only support RabbitMQ `3.3+`.
62
- Bunny `1.4.x` and earlier supports RabbitMQ 2.x and 3.x.
67
+ Modern Bunny releases target [currently supported RabbitMQ release series](https://www.rabbitmq.com/versions.html).
63
68
 
64
69
 
65
70
  ## Change Log
@@ -69,9 +74,8 @@ a stable public API.
69
74
 
70
75
  Change logs per release series:
71
76
 
72
- * [master](https://github.com/ruby-amqp/bunny/blob/master/ChangeLog.md)
73
- * [2.18.x](https://github.com/ruby-amqp/bunny/blob/2.18.x-stable/ChangeLog.md)
74
- * [2.17.x](https://github.com/ruby-amqp/bunny/blob/2.17.x-stable/ChangeLog.md)
77
+ * [main](https://github.com/ruby-amqp/bunny/blob/main/ChangeLog.md) (most notable changes for all release series)
78
+ * [2.19.x](https://github.com/ruby-amqp/bunny/blob/2.19.x-stable/ChangeLog.md)
75
79
 
76
80
 
77
81
 
@@ -81,20 +85,20 @@ Change logs per release series:
81
85
 
82
86
  [![Gem Version](https://badge.fury.io/rb/bunny.svg)](http://badge.fury.io/rb/bunny)
83
87
 
84
- ### With Rubygems
88
+ ### Bundler Dependency
85
89
 
86
- To install Bunny with RubyGems:
90
+ To use Bunny in a project managed with Bundler:
87
91
 
88
- ```
89
- gem install bunny
92
+ ``` ruby
93
+ gem "bunny", ">= 2.19.0"
90
94
  ```
91
95
 
92
- ### Bundler Dependency
96
+ ### With Rubygems
93
97
 
94
- To use Bunny in a project managed with Bundler:
98
+ To install Bunny with RubyGems:
95
99
 
96
- ``` ruby
97
- gem "bunny", ">= 2.18.0"
100
+ ```
101
+ gem install bunny
98
102
  ```
99
103
 
100
104
 
@@ -103,7 +107,7 @@ gem "bunny", ">= 2.18.0"
103
107
  Below is a small snippet that demonstrates how to publish
104
108
  and synchronously consume ("pull API") messages with Bunny.
105
109
 
106
- For a 15 minute tutorial using more practical examples, see [Getting Started with RabbitMQ and Ruby using Bunny](http://rubybunny.info/articles/getting_started.html).
110
+ For a 15 minute tutorial using more practical examples, see [Getting Started with RabbitMQ and Ruby using Bunny](https://www.rabbitmq.com/tutorials/tutorial-one-ruby.html).
107
111
 
108
112
  ``` ruby
109
113
  require "bunny"
@@ -150,23 +154,25 @@ For a 15 minute tutorial using more practical examples, see [Getting Started wit
150
154
 
151
155
  ### Guides
152
156
 
153
- Bunny documentation guides are available at [rubybunny.info](http://rubybunny.info):
157
+ Bunny documentation guides are [under `docs/guides` in this repository](https://github.com/ruby-amqp/bunny/tree/main/docs/guides):
154
158
 
155
- * [Queues and Consumers](http://rubybunny.info/articles/queues.html)
156
- * [Exchanges and Publishers](http://rubybunny.info/articles/exchanges.html)
159
+ * [Queues and Consumers](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/queues.md)
160
+ * [Exchanges and Publishers](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/exchanges.md)
157
161
  * [AMQP 0.9.1 Model Explained](http://www.rabbitmq.com/tutorials/amqp-concepts.html)
158
- * [Connecting to RabbitMQ](http://rubybunny.info/articles/connecting.html)
159
- * [Error Handling and Recovery](http://rubybunny.info/articles/error_handling.html)
160
- * [TLS/SSL Support](http://rubybunny.info/articles/tls.html)
161
- * [Bindings](http://rubybunny.info/articles/bindings.html)
162
- * [Using RabbitMQ Extensions with Bunny](http://rubybunny.info/articles/extensions.html)
163
- * [Durability and Related Matters](http://rubybunny.info/articles/durability.html)
162
+ * [Connecting to RabbitMQ](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/connecting.md)
163
+ * [Error Handling and Recovery](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/error_handling.md)
164
+ * [TLS/SSL Support](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/tls.md)
165
+ * [Bindings](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/bindings.md)
166
+ * [Using RabbitMQ Extensions with Bunny](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/extensions.md)
167
+ * [Durability and Related Matters](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/durability.md)
164
168
 
165
169
  Some highly relevant RabbitMQ documentation guides:
166
170
 
167
171
  * [Connections](https://www.rabbitmq.com/connections.html)
168
172
  * [Channels](https://www.rabbitmq.com/channels.html)
169
173
  * [Queues](https://www.rabbitmq.com/queues.html)
174
+ * [Quorum queues](https://www.rabbitmq.com/quorum-queues.html)
175
+ * [Streams](https://rabbitmq.com/streams.html) (Bunny can perform basic operations on streams even though it does not implement the [RabbitMQ Stream protocol](https://github.com/rabbitmq/rabbitmq-server/blob/v3.10.x/deps/rabbitmq_stream/docs/PROTOCOL.adoc))
170
176
  * [Publishers](https://www.rabbitmq.com/publishers.html)
171
177
  * [Consumers](https://www.rabbitmq.com/consumers.html)
172
178
  * Data safety: publisher and consumer [Confirmations](https://www.rabbitmq.com/confirms.html)
@@ -194,13 +200,6 @@ mailing list. Feel free to ask any questions that you may have.
194
200
  [![Build Status](https://travis-ci.org/ruby-amqp/bunny.svg)](https://travis-ci.org/ruby-amqp/bunny/)
195
201
 
196
202
 
197
- ### News & Announcements on Twitter
198
-
199
- To subscribe for announcements of releases, important changes and so on, please follow [@rubyamqp](https://twitter.com/#!/rubyamqp) on Twitter.
200
-
201
- More detailed announcements can be found in the [RabbitMQ Ruby clients blog](http://blog.rubyrabbitmq.info).
202
-
203
-
204
203
  ### Reporting Issues
205
204
 
206
205
  If you find a bug you understand well, poor default, incorrect or unclear piece of documentation,
data/lib/bunny/channel.rb CHANGED
@@ -143,6 +143,10 @@ module Bunny
143
143
  attr_reader :work_pool
144
144
  # @return [Integer] Next publisher confirmations sequence index
145
145
  attr_reader :next_publish_seq_no
146
+ # @return [Integer] Offset for the confirmations sequence index.
147
+ # This will be set to the current sequence index during automatic network failure recovery
148
+ # to keep the sequence monotonic for the user and abstract the reset from the protocol
149
+ attr_reader :delivery_tag_offset
146
150
  # @return [Hash<String, Bunny::Queue>] Queue instances declared on this channel
147
151
  attr_reader :queues
148
152
  # @return [Hash<String, Bunny::Exchange>] Exchange instances declared on this channel
@@ -374,7 +378,7 @@ module Bunny
374
378
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
375
379
  # @api public
376
380
  def default_exchange
377
- Exchange.default(self)
381
+ @default_exchange ||= Exchange.default(self)
378
382
  end
379
383
 
380
384
  # Declares a headers exchange or looks it up in the cache of previously
@@ -392,7 +396,7 @@ module Bunny
392
396
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
393
397
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
394
398
  def exchange(name, opts = {})
395
- Exchange.new(self, opts.fetch(:type, :direct), name, opts)
399
+ find_exchange(name) || Exchange.new(self, opts.fetch(:type, :direct), name, opts)
396
400
  end
397
401
 
398
402
  # @endgroup
@@ -408,7 +412,7 @@ module Bunny
408
412
  # @option opts [Boolean] :durable (false) Should this queue be durable?
409
413
  # @option opts [Boolean] :auto-delete (false) Should this queue be automatically deleted when the last consumer disconnects?
410
414
  # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
411
- # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
415
+ # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
412
416
  #
413
417
  # @return [Bunny::Queue] Queue that was declared or looked up in the cache
414
418
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
@@ -422,6 +426,73 @@ module Bunny
422
426
  register_queue(q)
423
427
  end
424
428
 
429
+ # Declares a new client-named quorum queue.
430
+ #
431
+ # @param [String] name Queue name. Empty (server-generated) names are not supported by this method.
432
+ # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored.
433
+ #
434
+ # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
435
+ #
436
+ # @return [Bunny::Queue] Queue that was declared
437
+ # @see #durable_queue
438
+ # @see #queue
439
+ # @api public
440
+ def quorum_queue(name, opts = {})
441
+ throw ArgumentError.new("quorum queue name must not be nil") if name.nil?
442
+ throw ArgumentError.new("quorum queue name must not be empty (server-named QQs do not make sense)") if name.empty?
443
+
444
+ durable_queue(name, Bunny::Queue::Types::QUORUM, opts)
445
+ end
446
+
447
+ # Declares a new client-named stream (that Bunny can use as if it was a queue).
448
+ # Note that Bunny would still use AMQP 0-9-1 to perform operations on this "queue".
449
+ # To use stream-specific operations and to gain from stream protocol efficiency and partitioning,
450
+ # use a Ruby client for the RabbitMQ stream protocol.
451
+ #
452
+ # @param [String] name Stream name. Empty (server-generated) names are not supported by this method.
453
+ # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored.
454
+ #
455
+ # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
456
+ #
457
+ #
458
+ # @return [Bunny::Queue] Queue that was declared
459
+ # @see #durable_queue
460
+ # @see #queue
461
+ # @api public
462
+ def stream(name, opts = {})
463
+ throw ArgumentError.new("stream name must not be nil") if name.nil?
464
+ throw ArgumentError.new("stream name must not be empty (server-named QQs do not make sense)") if name.empty?
465
+
466
+ durable_queue(name, Bunny::Queue::Types::STREAM, opts)
467
+ end
468
+
469
+ # Declares a new server-named queue that is automatically deleted when the
470
+ # connection is closed.
471
+ #
472
+ # @param [String] name Queue name. Empty (server-generated) names are not supported by this method.
473
+ # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored.
474
+ #
475
+ # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
476
+ #
477
+ # @return [Bunny::Queue] Queue that was declared
478
+ # @see #queue
479
+ # @api public
480
+ def durable_queue(name, type = "classic", opts = {})
481
+ throw ArgumentError.new("queue name must not be nil") if name.nil?
482
+ throw ArgumentError.new("queue name must not be empty (server-named durable queues do not make sense)") if name.empty?
483
+
484
+ final_opts = opts.merge({
485
+ :type => type,
486
+ :durable => true,
487
+ # exclusive or auto-delete QQs do not make much sense
488
+ :exclusive => false,
489
+ :auto_delete => false
490
+ })
491
+ q = find_queue(name) || Bunny::Queue.new(self, name, final_opts)
492
+
493
+ register_queue(q)
494
+ end
495
+
425
496
  # Declares a new server-named queue that is automatically deleted when the
426
497
  # connection is closed.
427
498
  #
@@ -1525,12 +1596,15 @@ module Bunny
1525
1596
 
1526
1597
  # Recovers publisher confirms mode. Used by the Automatic Network Failure
1527
1598
  # Recovery feature.
1599
+ # Set the offset to the previous publish sequence index as the protocol will reset the index to after recovery.
1528
1600
  #
1529
1601
  # @api plugin
1530
1602
  def recover_confirm_mode
1531
1603
  if using_publisher_confirmations?
1532
- @unconfirmed_set.clear
1533
- @delivery_tag_offset = @next_publish_seq_no - 1
1604
+ @unconfirmed_set_mutex.synchronize do
1605
+ @unconfirmed_set.clear
1606
+ @delivery_tag_offset = @next_publish_seq_no - 1
1607
+ end
1534
1608
  confirm_select(@confirms_callback)
1535
1609
  end
1536
1610
  end
@@ -1599,7 +1673,7 @@ module Bunny
1599
1673
 
1600
1674
  # @return [String] Brief human-readable representation of the channel
1601
1675
  def to_s
1602
- "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}> @open=#{open?}"
1676
+ "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s} @open=#{open?}>"
1603
1677
  end
1604
1678
 
1605
1679
  def inspect
@@ -1663,7 +1737,7 @@ module Bunny
1663
1737
  if !pending_server_named_queue_declaration?
1664
1738
  # this response is for an outdated/overwritten
1665
1739
  # 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"
1740
+ @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"
1667
1741
  end
1668
1742
  end
1669
1743
  when AMQ::Protocol::Queue::DeleteOk then
@@ -1786,14 +1860,16 @@ module Bunny
1786
1860
  end
1787
1861
  end
1788
1862
 
1863
+ # Handle delivery tag offset calculations to keep the the delivery tag monotonic after a reset
1864
+ # due to automatic network failure recovery. @unconfirmed_set contains indices already offsetted.
1789
1865
  # @private
1790
1866
  def handle_ack_or_nack(delivery_tag_before_offset, multiple, nack)
1791
- delivery_tag = delivery_tag_before_offset + @delivery_tag_offset
1792
- confirmed_range_start = multiple ? @delivery_tag_offset + @unconfirmed_set.min : delivery_tag
1793
- confirmed_range_end = delivery_tag
1794
- confirmed_range = (confirmed_range_start..confirmed_range_end)
1795
-
1796
1867
  @unconfirmed_set_mutex.synchronize do
1868
+ delivery_tag = delivery_tag_before_offset + @delivery_tag_offset
1869
+ confirmed_range_start = multiple ? @unconfirmed_set.min : delivery_tag
1870
+ confirmed_range_end = delivery_tag
1871
+ confirmed_range = (confirmed_range_start..confirmed_range_end)
1872
+
1797
1873
  if nack
1798
1874
  @nacked_set.merge(@unconfirmed_set & confirmed_range)
1799
1875
  end
@@ -86,12 +86,12 @@ module Bunny
86
86
 
87
87
  # @return [String] More detailed human-readable string representation of this consumer
88
88
  def inspect
89
- "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag} @exclusive=#{@exclusive} @no_ack=#{@no_ack}>"
89
+ "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name} @consumer_tag=#{@consumer_tag} @exclusive=#{@exclusive} @no_ack=#{@no_ack}>"
90
90
  end
91
91
 
92
92
  # @return [String] Brief human-readable string representation of this consumer
93
93
  def to_s
94
- "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name}> @consumer_tag=#{@consumer_tag}>"
94
+ "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name} @consumer_tag=#{@consumer_tag}>"
95
95
  end
96
96
 
97
97
  # @return [Boolean] true if this consumer uses automatic acknowledgement mode
@@ -70,7 +70,7 @@ module Bunny
70
70
  return if !(wait_for_workers && @shutdown_timeout && was_running)
71
71
 
72
72
  @shutdown_mutex.synchronize do
73
- @shutdown_conditional.wait(@shutdown_mutex, @shutdown_timeout)
73
+ @shutdown_conditional.wait(@shutdown_mutex, @shutdown_timeout) if busy?
74
74
  end
75
75
  end
76
76
 
@@ -37,7 +37,7 @@ module Bunny
37
37
  @channel = channel
38
38
  end
39
39
 
40
- # Iterates over the delivery properties
40
+ # Iterates over delivery properties
41
41
  # @see Enumerable#each
42
42
  def each(*args, &block)
43
43
  @hash.each(*args, &block)
data/lib/bunny/queue.rb CHANGED
@@ -11,6 +11,23 @@ module Bunny
11
11
  # API
12
12
  #
13
13
 
14
+ module Types
15
+ QUORUM = "quorum"
16
+ CLASSIC = "classic"
17
+ STREAM = "stream"
18
+
19
+ KNOWN = [CLASSIC, QUORUM, STREAM]
20
+
21
+ def self.known?(q_type)
22
+ KNOWN.include?(q_type)
23
+ end
24
+ end
25
+
26
+ module XArgs
27
+ MAX_LENGTH = "x-max-length",
28
+ QUEUE_TYPE = "x-queue-type"
29
+ end
30
+
14
31
  # @return [Bunny::Channel] Channel this queue uses
15
32
  attr_reader :channel
16
33
  # @return [String] Queue name
@@ -25,7 +42,8 @@ module Bunny
25
42
  # @option opts [Boolean] :durable (false) Should this queue be durable?
26
43
  # @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects?
27
44
  # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
28
- # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
45
+ # @option opts [String] :type (nil) Type of the declared queue (classic, quorum or stream)
46
+ # @option opts [Hash] :arguments (nil) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
29
47
  #
30
48
  # @see Bunny::Channel#queue
31
49
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
@@ -42,7 +60,17 @@ module Bunny
42
60
  @exclusive = @options[:exclusive]
43
61
  @server_named = @name.empty?
44
62
  @auto_delete = @options[:auto_delete]
45
- @arguments = @options[:arguments]
63
+ @type = @options[:type]
64
+
65
+ @arguments = if @type and !@type.empty? then
66
+ (@options[:arguments] || {}).merge({XArgs::QUEUE_TYPE => @type})
67
+ else
68
+ @options[:arguments]
69
+ end
70
+ verify_type!(@arguments)
71
+ # reassigns updated and verified arguments because Bunny::Channel#declare_queue
72
+ # accepts a map of options
73
+ @options[:arguments] = @arguments
46
74
 
47
75
  @bindings = Array.new
48
76
 
@@ -389,5 +417,11 @@ module Bunny
389
417
  h
390
418
  end
391
419
  end
420
+
421
+ def verify_type!(args)
422
+ q_type = (args || {})["x-queue-type"]
423
+ throw ArgumentError.new(
424
+ "unsupported queue type #{q_type.inspect}, supported ones: #{Types::KNOWN.join(', ')}") if (q_type and !Types.known?(q_type))
425
+ end
392
426
  end
393
427
  end
data/lib/bunny/session.rb CHANGED
@@ -71,6 +71,7 @@ module Bunny
71
71
  # Default reconnection interval for TCP connection failures
72
72
  DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
73
73
 
74
+ DEFAULT_RECOVERABLE_EXCEPTIONS = [StandardError, TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError, SystemCallError, Timeout::Error, Bunny::ConnectionLevelException, Bunny::ConnectionClosedError]
74
75
 
75
76
  #
76
77
  # API
@@ -91,6 +92,7 @@ module Bunny
91
92
  attr_reader :network_recovery_interval
92
93
  attr_reader :connection_name
93
94
  attr_accessor :socket_configurator
95
+ attr_accessor :recoverable_exceptions
94
96
 
95
97
  # @param [String, Hash] connection_string_or_opts Connection string or a hash of connection options
96
98
  # @param [Hash] optz Extra options not related to connection
@@ -109,7 +111,7 @@ module Bunny
109
111
  # @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem)
110
112
  # @option connection_string_or_opts [Array<String>] :tls_ca_certificates Array of paths to TLS/SSL CA files (.pem), by default detected from OpenSSL configuration
111
113
  # @option connection_string_or_opts [String] :verify_peer (true) Whether TLS peer verification should be performed
112
- # @option connection_string_or_opts [Symbol] :tls_version (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2)
114
+ # @option connection_string_or_opts [Symbol] :tls_protocol (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2)
113
115
  # @option connection_string_or_opts [Integer] :channel_max (2047) Maximum number of channels allowed on this connection, minus 1 to account for the special channel 0.
114
116
  # @option connection_string_or_opts [Integer] :continuation_timeout (15000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds.
115
117
  # @option connection_string_or_opts [Integer] :connection_timeout (30) Timeout in seconds for connecting to the server.
@@ -126,6 +128,7 @@ module Bunny
126
128
  # @option connection_string_or_opts [Integer] :reset_recovery_attempts_after_reconnection (true) Should recovery attempt counter be reset after successful reconnection? When set to false, the attempt counter will last through the entire lifetime of the connection object.
127
129
  # @option connection_string_or_opts [Proc] :recovery_attempt_started (nil) Will be called before every connection recovery attempt
128
130
  # @option connection_string_or_opts [Proc] :recovery_completed (nil) Will be called after successful connection recovery
131
+ # @option connection_string_or_opts [Proc] :recovery_attempts_exhausted (nil) Will be called when the connection recovery failed after the specified amount of recovery attempts
129
132
  # @option connection_string_or_opts [Boolean] :recover_from_connection_close (true) Should this connection recover after receiving a server-sent connection.close (e.g. connection was force closed)?
130
133
  # @option connection_string_or_opts [Object] :session_error_handler (Thread.current) Object which responds to #raise that will act as a session error handler. Defaults to Thread.current, which will raise asynchronous exceptions in the thread that created the session.
131
134
  #
@@ -223,9 +226,12 @@ module Bunny
223
226
 
224
227
  @recovery_attempt_started = opts[:recovery_attempt_started]
225
228
  @recovery_completed = opts[:recovery_completed]
229
+ @recovery_attempts_exhausted = opts[:recovery_attempts_exhausted]
226
230
 
227
231
  @session_error_handler = opts.fetch(:session_error_handler, Thread.current)
228
232
 
233
+ @recoverable_exceptions = DEFAULT_RECOVERABLE_EXCEPTIONS.dup
234
+
229
235
  self.reset_continuations
230
236
  self.initialize_transport
231
237
 
@@ -549,6 +555,12 @@ module Bunny
549
555
  @recovery_completed = block
550
556
  end
551
557
 
558
+ # Defines a callable (e.g. a block) that will be called
559
+ # when the connection recovery failed after the specified
560
+ # numbers of recovery attempts.
561
+ def after_recovery_attempts_exhausted(&block)
562
+ @recovery_attempts_exhausted = block
563
+ end
552
564
 
553
565
  #
554
566
  # Implementation
@@ -671,8 +683,12 @@ module Bunny
671
683
  # avoid doing that while holding a mutex lock. MK.
672
684
  ch.handle_method(method)
673
685
  ensure
674
- # synchronises on @channel_mutex under the hood
675
- self.unregister_channel(ch)
686
+ if ch.nil?
687
+ @logger.warn "Received a server-sent channel.close but the channel was not found locally. Ignoring the frame."
688
+ else
689
+ # synchronises on @channel_mutex under the hood
690
+ self.unregister_channel(ch)
691
+ end
676
692
  end
677
693
  when AMQ::Protocol::Basic::GetEmpty then
678
694
  ch = find_channel(ch_number)
@@ -743,9 +759,7 @@ module Bunny
743
759
 
744
760
  # @private
745
761
  def recoverable_network_failure?(exception)
746
- # No reasonably smart strategy was suggested in a few years.
747
- # So just recover unconditionally. MK.
748
- true
762
+ @recoverable_exceptions.any? {|x| exception.kind_of? x}
749
763
  end
750
764
 
751
765
  # @private
@@ -790,19 +804,23 @@ module Bunny
790
804
  rescue HostListDepleted
791
805
  reset_address_index
792
806
  retry
793
- rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError, SystemCallError, Timeout::Error => e
794
- @logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds"
795
- if should_retry_recovery?
796
- decrement_recovery_attemp_counter!
797
- if recoverable_network_failure?(e)
807
+ rescue => e
808
+ if recoverable_network_failure?(e)
809
+ @logger.warn "TCP connection failed"
810
+ if should_retry_recovery?
811
+ @logger.warn "Reconnecting in #{@network_recovery_interval} seconds"
812
+ decrement_recovery_attemp_counter!
798
813
  announce_network_failure_recovery
799
814
  retry
815
+ else
816
+ @logger.error "Ran out of recovery attempts (limit set to #{@max_recovery_attempts}), giving up"
817
+ @transport.close
818
+ self.close(false)
819
+ @manually_closed = false
820
+ notify_of_recovery_attempts_exhausted
800
821
  end
801
822
  else
802
- @logger.error "Ran out of recovery attempts (limit set to #{@max_recovery_attempts}), giving up"
803
- @transport.close
804
- self.close(false)
805
- @manually_closed = false
823
+ raise e
806
824
  end
807
825
  end
808
826
 
@@ -850,6 +868,11 @@ module Bunny
850
868
  @recovery_completed.call if @recovery_completed
851
869
  end
852
870
 
871
+ # @private
872
+ def notify_of_recovery_attempts_exhausted
873
+ @recovery_attempts_exhausted.call if @recovery_attempts_exhausted
874
+ end
875
+
853
876
  # @private
854
877
  def instantiate_connection_level_exception(frame)
855
878
  case frame
@@ -1352,7 +1375,7 @@ module Bunny
1352
1375
  host_from_address(address),
1353
1376
  port_from_address(address),
1354
1377
  @opts.merge(:session_error_handler => @session_error_handler)
1355
- )
1378
+ )
1356
1379
 
1357
1380
  # Reset the cached progname for the logger only when no logger was provided
1358
1381
  @default_logger.progname = self.to_s
@@ -25,6 +25,30 @@ module Bunny
25
25
  DEFAULT_READ_TIMEOUT = 30.0
26
26
  DEFAULT_WRITE_TIMEOUT = 30.0
27
27
 
28
+ # mimics METHODS_MAP in ssl.rb but also lists TLS 1.3
29
+ # and string constants
30
+ TLS_VERSION_ALIASES = {
31
+ TLSv1: OpenSSL::SSL::TLS1_VERSION,
32
+ TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION,
33
+ TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION,
34
+ "1.0": OpenSSL::SSL::TLS1_VERSION,
35
+ "1.1": OpenSSL::SSL::TLS1_1_VERSION,
36
+ "1.2": OpenSSL::SSL::TLS1_2_VERSION,
37
+ OpenSSL::SSL::TLS1_VERSION => OpenSSL::SSL::TLS1_VERSION,
38
+ OpenSSL::SSL::TLS1_1_VERSION => OpenSSL::SSL::TLS1_1_VERSION,
39
+ OpenSSL::SSL::TLS1_2_VERSION => OpenSSL::SSL::TLS1_2_VERSION
40
+ }
41
+
42
+ # older OpenSSL versions won't support for TLS 1.3 and won't
43
+ # have this constant defined.
44
+ if defined?(OpenSSL::SSL::TLS1_3_VERSION)
45
+ TLS_VERSION_ALIASES["1.3"] = OpenSSL::SSL::TLS1_3_VERSION
46
+ TLS_VERSION_ALIASES[:TLSv1_3] = OpenSSL::SSL::TLS1_3_VERSION
47
+ TLS_VERSION_ALIASES[OpenSSL::SSL::TLS1_3_VERSION] = OpenSSL::SSL::TLS1_3_VERSION
48
+ end
49
+
50
+ TLS_VERSION_ALIASES.freeze
51
+
28
52
  attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
29
53
  attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
30
54
 
@@ -491,7 +515,11 @@ but prone to man-in-the-middle attacks. Please set verify_peer: true in producti
491
515
  end
492
516
 
493
517
  ssl_version = opts[:tls_protocol] || opts[:ssl_version] || :TLSv1_2
494
- ctx.ssl_version = ssl_version if ssl_version
518
+ if ssl_version
519
+ v = tls_version_constant(ssl_version)
520
+ ctx.min_version = v
521
+ ctx.max_version = v
522
+ end
495
523
 
496
524
  ctx
497
525
  end
@@ -519,6 +547,14 @@ but prone to man-in-the-middle attacks. Please set verify_peer: true in producti
519
547
  end
520
548
  end
521
549
 
550
+
551
+ def tls_version_constant(value)
552
+ # OpenSSL::SSL::TLS1_3_VERSION and similar constants
553
+ # are just integers, so use the value itself as fallback since
554
+ # there is no class to case switch on
555
+ TLS_VERSION_ALIASES[value] || value
556
+ end
557
+
522
558
  def timeout_from(options)
523
559
  options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT
524
560
  end
data/lib/bunny/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "2.19.0"
5
+ VERSION = "2.22.0"
6
6
  end