bunny 2.7.4 → 2.22.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 (156) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +61 -35
  3. data/lib/bunny/channel.rb +186 -50
  4. data/lib/bunny/channel_id_allocator.rb +3 -1
  5. data/lib/bunny/consumer.rb +2 -2
  6. data/lib/bunny/consumer_work_pool.rb +2 -1
  7. data/lib/bunny/cruby/socket.rb +3 -0
  8. data/lib/bunny/cruby/ssl_socket.rb +6 -1
  9. data/lib/bunny/delivery_info.rb +1 -1
  10. data/lib/bunny/heartbeat_sender.rb +2 -1
  11. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  12. data/lib/bunny/queue.rb +36 -8
  13. data/lib/bunny/reader_loop.rb +22 -10
  14. data/lib/bunny/session.rb +152 -65
  15. data/lib/bunny/test_kit.rb +14 -0
  16. data/lib/bunny/transport.rb +132 -49
  17. data/lib/bunny/version.rb +1 -1
  18. data/lib/bunny.rb +45 -4
  19. metadata +37 -225
  20. data/.github/ISSUE_TEMPLATE.md +0 -18
  21. data/.gitignore +0 -28
  22. data/.rspec +0 -1
  23. data/.travis.yml +0 -20
  24. data/.yardopts +0 -8
  25. data/CONTRIBUTING.md +0 -111
  26. data/ChangeLog.md +0 -1831
  27. data/Gemfile +0 -53
  28. data/LICENSE +0 -21
  29. data/Rakefile +0 -46
  30. data/benchmarks/basic_publish/with_128K_messages.rb +0 -35
  31. data/benchmarks/basic_publish/with_1k_messages.rb +0 -35
  32. data/benchmarks/basic_publish/with_4K_messages.rb +0 -35
  33. data/benchmarks/basic_publish/with_64K_messages.rb +0 -35
  34. data/benchmarks/channel_open.rb +0 -28
  35. data/benchmarks/mutex_and_monitor.rb +0 -42
  36. data/benchmarks/queue_declare.rb +0 -29
  37. data/benchmarks/queue_declare_and_bind.rb +0 -29
  38. data/benchmarks/queue_declare_bind_and_delete.rb +0 -29
  39. data/benchmarks/synchronized_sorted_set.rb +0 -53
  40. data/benchmarks/write_vs_write_nonblock.rb +0 -49
  41. data/bin/ci/before_build +0 -46
  42. data/bunny.gemspec +0 -35
  43. data/docker/Dockerfile +0 -16
  44. data/docker/docker-entrypoint.sh +0 -37
  45. data/docker-compose.yml +0 -18
  46. data/examples/connection/authentication_failure.rb +0 -16
  47. data/examples/connection/automatic_recovery_with_basic_get.rb +0 -40
  48. data/examples/connection/automatic_recovery_with_client_named_queues.rb +0 -36
  49. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +0 -46
  50. data/examples/connection/automatic_recovery_with_republishing.rb +0 -109
  51. data/examples/connection/automatic_recovery_with_server_named_queues.rb +0 -35
  52. data/examples/connection/channel_level_exception.rb +0 -27
  53. data/examples/connection/disabled_automatic_recovery.rb +0 -34
  54. data/examples/connection/heartbeat.rb +0 -17
  55. data/examples/connection/manually_reconnecting_consumer.rb +0 -23
  56. data/examples/connection/manually_reconnecting_publisher.rb +0 -28
  57. data/examples/connection/unknown_host.rb +0 -16
  58. data/examples/consumers/high_and_low_priority.rb +0 -50
  59. data/examples/guides/exchanges/direct_exchange_routing.rb +0 -36
  60. data/examples/guides/exchanges/fanout_exchange_routing.rb +0 -28
  61. data/examples/guides/exchanges/headers_exchange_routing.rb +0 -31
  62. data/examples/guides/exchanges/mandatory_messages.rb +0 -30
  63. data/examples/guides/extensions/alternate_exchange.rb +0 -30
  64. data/examples/guides/extensions/basic_nack.rb +0 -33
  65. data/examples/guides/extensions/connection_blocked.rb +0 -35
  66. data/examples/guides/extensions/consumer_cancellation_notification.rb +0 -39
  67. data/examples/guides/extensions/dead_letter_exchange.rb +0 -32
  68. data/examples/guides/extensions/exchange_to_exchange_bindings.rb +0 -29
  69. data/examples/guides/extensions/per_message_ttl.rb +0 -36
  70. data/examples/guides/extensions/per_queue_message_ttl.rb +0 -36
  71. data/examples/guides/extensions/publisher_confirms.rb +0 -28
  72. data/examples/guides/extensions/queue_lease.rb +0 -26
  73. data/examples/guides/extensions/sender_selected_distribution.rb +0 -32
  74. data/examples/guides/getting_started/blabbr.rb +0 -27
  75. data/examples/guides/getting_started/hello_world.rb +0 -22
  76. data/examples/guides/getting_started/weathr.rb +0 -49
  77. data/examples/guides/queues/one_off_consumer.rb +0 -25
  78. data/examples/guides/queues/redeliveries.rb +0 -81
  79. data/profiling/basic_publish/with_4K_messages.rb +0 -33
  80. data/repl +0 -3
  81. data/spec/config/enabled_plugins +0 -1
  82. data/spec/config/rabbitmq.config +0 -19
  83. data/spec/higher_level_api/integration/basic_ack_spec.rb +0 -230
  84. data/spec/higher_level_api/integration/basic_cancel_spec.rb +0 -142
  85. data/spec/higher_level_api/integration/basic_consume_spec.rb +0 -349
  86. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +0 -54
  87. data/spec/higher_level_api/integration/basic_get_spec.rb +0 -80
  88. data/spec/higher_level_api/integration/basic_nack_spec.rb +0 -82
  89. data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -74
  90. data/spec/higher_level_api/integration/basic_qos_spec.rb +0 -57
  91. data/spec/higher_level_api/integration/basic_reject_spec.rb +0 -152
  92. data/spec/higher_level_api/integration/basic_return_spec.rb +0 -33
  93. data/spec/higher_level_api/integration/channel_close_spec.rb +0 -25
  94. data/spec/higher_level_api/integration/channel_open_spec.rb +0 -57
  95. data/spec/higher_level_api/integration/connection_recovery_spec.rb +0 -471
  96. data/spec/higher_level_api/integration/connection_spec.rb +0 -559
  97. data/spec/higher_level_api/integration/connection_stop_spec.rb +0 -83
  98. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +0 -128
  99. data/spec/higher_level_api/integration/dead_lettering_spec.rb +0 -75
  100. data/spec/higher_level_api/integration/exchange_bind_spec.rb +0 -31
  101. data/spec/higher_level_api/integration/exchange_declare_spec.rb +0 -237
  102. data/spec/higher_level_api/integration/exchange_delete_spec.rb +0 -105
  103. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +0 -40
  104. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +0 -28
  105. data/spec/higher_level_api/integration/heartbeat_spec.rb +0 -49
  106. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  107. data/spec/higher_level_api/integration/message_properties_access_spec.rb +0 -95
  108. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +0 -24
  109. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +0 -191
  110. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +0 -87
  111. data/spec/higher_level_api/integration/queue_bind_spec.rb +0 -109
  112. data/spec/higher_level_api/integration/queue_declare_spec.rb +0 -221
  113. data/spec/higher_level_api/integration/queue_delete_spec.rb +0 -41
  114. data/spec/higher_level_api/integration/queue_purge_spec.rb +0 -30
  115. data/spec/higher_level_api/integration/queue_unbind_spec.rb +0 -54
  116. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +0 -60
  117. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +0 -36
  118. data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -222
  119. data/spec/higher_level_api/integration/tx_commit_spec.rb +0 -21
  120. data/spec/higher_level_api/integration/tx_rollback_spec.rb +0 -21
  121. data/spec/higher_level_api/integration/with_channel_spec.rb +0 -25
  122. data/spec/issues/issue100_spec.rb +0 -42
  123. data/spec/issues/issue141_spec.rb +0 -43
  124. data/spec/issues/issue202_spec.rb +0 -15
  125. data/spec/issues/issue224_spec.rb +0 -40
  126. data/spec/issues/issue465_spec.rb +0 -32
  127. data/spec/issues/issue78_spec.rb +0 -72
  128. data/spec/issues/issue83_spec.rb +0 -30
  129. data/spec/issues/issue97_attachment.json +0 -1
  130. data/spec/issues/issue97_spec.rb +0 -175
  131. data/spec/lower_level_api/integration/basic_cancel_spec.rb +0 -83
  132. data/spec/lower_level_api/integration/basic_consume_spec.rb +0 -99
  133. data/spec/spec_helper.rb +0 -51
  134. data/spec/stress/channel_close_stress_spec.rb +0 -64
  135. data/spec/stress/channel_open_stress_spec.rb +0 -84
  136. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -28
  137. data/spec/stress/concurrent_consumers_stress_spec.rb +0 -71
  138. data/spec/stress/concurrent_publishers_stress_spec.rb +0 -54
  139. data/spec/stress/connection_open_close_spec.rb +0 -52
  140. data/spec/stress/long_running_consumer_spec.rb +0 -84
  141. data/spec/tls/ca_certificate.pem +0 -29
  142. data/spec/tls/ca_key.pem +0 -52
  143. data/spec/tls/client_certificate.pem +0 -29
  144. data/spec/tls/client_key.pem +0 -51
  145. data/spec/tls/generate-server-cert.sh +0 -8
  146. data/spec/tls/server-openssl.cnf +0 -10
  147. data/spec/tls/server.csr +0 -16
  148. data/spec/tls/server_certificate.pem +0 -29
  149. data/spec/tls/server_key.pem +0 -51
  150. data/spec/unit/bunny_spec.rb +0 -15
  151. data/spec/unit/concurrent/atomic_fixnum_spec.rb +0 -35
  152. data/spec/unit/concurrent/condition_spec.rb +0 -82
  153. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +0 -35
  154. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +0 -73
  155. data/spec/unit/exchange_recovery_spec.rb +0 -39
  156. data/spec/unit/version_delivery_tag_spec.rb +0 -28
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
@@ -169,6 +173,17 @@ module Bunny
169
173
  @connection = connection
170
174
  @logger = connection.logger
171
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
+
172
187
  @status = :opening
173
188
 
174
189
  @connection.register_channel(self)
@@ -233,6 +248,9 @@ module Bunny
233
248
  # {Bunny::Queue}, {Bunny::Exchange} and {Bunny::Consumer} instances.
234
249
  # @api public
235
250
  def close
251
+ # see bunny#528
252
+ raise_if_no_longer_open!
253
+
236
254
  @connection.close_channel(self)
237
255
  @status = :closed
238
256
  @work_pool.shutdown
@@ -251,15 +269,6 @@ module Bunny
251
269
  @status == :closed
252
270
  end
253
271
 
254
- def to_s
255
- oid = ("0x%x" % (self.object_id << 1))
256
- "<#{self.class.name}:#{oid} number=#{@channel.id} @open=#{open?} connection=#{@connection.to_s}>"
257
- end
258
-
259
- def inspect
260
- to_s
261
- end
262
-
263
272
  #
264
273
  # @group Backwards compatibility with 0.8.0
265
274
  #
@@ -369,7 +378,7 @@ module Bunny
369
378
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
370
379
  # @api public
371
380
  def default_exchange
372
- Exchange.default(self)
381
+ @default_exchange ||= Exchange.default(self)
373
382
  end
374
383
 
375
384
  # Declares a headers exchange or looks it up in the cache of previously
@@ -387,7 +396,7 @@ module Bunny
387
396
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
388
397
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide
389
398
  def exchange(name, opts = {})
390
- Exchange.new(self, opts.fetch(:type, :direct), name, opts)
399
+ find_exchange(name) || Exchange.new(self, opts.fetch(:type, :direct), name, opts)
391
400
  end
392
401
 
393
402
  # @endgroup
@@ -403,18 +412,87 @@ module Bunny
403
412
  # @option opts [Boolean] :durable (false) Should this queue be durable?
404
413
  # @option opts [Boolean] :auto-delete (false) Should this queue be automatically deleted when the last consumer disconnects?
405
414
  # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
406
- # @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)
407
416
  #
408
417
  # @return [Bunny::Queue] Queue that was declared or looked up in the cache
409
418
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
410
419
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
411
420
  # @api public
412
421
  def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {})
422
+ throw ArgumentError.new("queue name must not be nil") if name.nil?
423
+
413
424
  q = find_queue(name) || Bunny::Queue.new(self, name, opts)
414
425
 
415
426
  register_queue(q)
416
427
  end
417
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
+
418
496
  # Declares a new server-named queue that is automatically deleted when the
419
497
  # connection is closed.
420
498
  #
@@ -643,7 +721,7 @@ module Bunny
643
721
 
644
722
  @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
645
723
 
646
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
724
+ with_continuation_timeout do
647
725
  @last_basic_qos_ok = wait_on_continuations
648
726
  end
649
727
  raise_if_continuation_resulted_in_a_channel_error!
@@ -664,7 +742,7 @@ module Bunny
664
742
  raise_if_no_longer_open!
665
743
 
666
744
  @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
667
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
745
+ with_continuation_timeout do
668
746
  @last_basic_recover_ok = wait_on_continuations
669
747
  end
670
748
  raise_if_continuation_resulted_in_a_channel_error!
@@ -871,7 +949,7 @@ module Bunny
871
949
  arguments))
872
950
 
873
951
  begin
874
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
952
+ with_continuation_timeout do
875
953
  @last_basic_consume_ok = wait_on_continuations
876
954
  end
877
955
  rescue Exception => e
@@ -921,7 +999,7 @@ module Bunny
921
999
  consumer.arguments))
922
1000
 
923
1001
  begin
924
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1002
+ with_continuation_timeout do
925
1003
  @last_basic_consume_ok = wait_on_continuations
926
1004
  end
927
1005
  rescue Exception => e
@@ -956,7 +1034,7 @@ module Bunny
956
1034
  def basic_cancel(consumer_tag)
957
1035
  @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
958
1036
 
959
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1037
+ with_continuation_timeout do
960
1038
  @last_basic_cancel_ok = wait_on_continuations
961
1039
  end
962
1040
 
@@ -980,7 +1058,8 @@ module Bunny
980
1058
 
981
1059
  # Declares a queue using queue.declare AMQP 0.9.1 method.
982
1060
  #
983
- # @param [String] name Queue name
1061
+ # @param [String] name The name of the queue or an empty string to let RabbitMQ generate a name.
1062
+ # Note that LF and CR characters will be stripped from the value.
984
1063
  # @param [Hash] opts Queue properties
985
1064
  #
986
1065
  # @option opts [Boolean] durable (false) Should information about this queue be persisted to disk so that it
@@ -998,16 +1077,28 @@ module Bunny
998
1077
  def queue_declare(name, opts = {})
999
1078
  raise_if_no_longer_open!
1000
1079
 
1001
- @connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
1002
- name,
1080
+ # strip trailing new line and carriage returns
1081
+ # just like RabbitMQ does
1082
+ safe_name = name.gsub(/[\r\n]/, "")
1083
+ @pending_queue_declare_name = safe_name
1084
+ @connection.send_frame(
1085
+ AMQ::Protocol::Queue::Declare.encode(@id,
1086
+ @pending_queue_declare_name,
1003
1087
  opts.fetch(:passive, false),
1004
1088
  opts.fetch(:durable, false),
1005
1089
  opts.fetch(:exclusive, false),
1006
1090
  opts.fetch(:auto_delete, false),
1007
1091
  false,
1008
1092
  opts[:arguments]))
1009
- @last_queue_declare_ok = wait_on_continuations
1010
1093
 
1094
+ begin
1095
+ with_continuation_timeout do
1096
+ @last_queue_declare_ok = wait_on_continuations
1097
+ end
1098
+ ensure
1099
+ # clear pending continuation context if it belongs to us
1100
+ @pending_queue_declare_name = nil if @pending_queue_declare_name == safe_name
1101
+ end
1011
1102
  raise_if_continuation_resulted_in_a_channel_error!
1012
1103
 
1013
1104
  @last_queue_declare_ok
@@ -1032,7 +1123,7 @@ module Bunny
1032
1123
  opts[:if_unused],
1033
1124
  opts[:if_empty],
1034
1125
  false))
1035
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1126
+ with_continuation_timeout do
1036
1127
  @last_queue_delete_ok = wait_on_continuations
1037
1128
  end
1038
1129
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1052,7 +1143,7 @@ module Bunny
1052
1143
 
1053
1144
  @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
1054
1145
 
1055
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1146
+ with_continuation_timeout do
1056
1147
  @last_queue_purge_ok = wait_on_continuations
1057
1148
  end
1058
1149
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1088,7 +1179,7 @@ module Bunny
1088
1179
  (opts[:routing_key] || opts[:key]),
1089
1180
  false,
1090
1181
  opts[:arguments]))
1091
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1182
+ with_continuation_timeout do
1092
1183
  @last_queue_bind_ok = wait_on_continuations
1093
1184
  end
1094
1185
 
@@ -1123,7 +1214,7 @@ module Bunny
1123
1214
  exchange_name,
1124
1215
  opts[:routing_key],
1125
1216
  opts[:arguments]))
1126
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1217
+ with_continuation_timeout do
1127
1218
  @last_queue_unbind_ok = wait_on_continuations
1128
1219
  end
1129
1220
 
@@ -1136,26 +1227,30 @@ module Bunny
1136
1227
 
1137
1228
  # @group Exchange operations (exchange.*)
1138
1229
 
1139
- # Declares a echange using echange.declare AMQP 0.9.1 method.
1230
+ # Declares a exchange using exchange.declare AMQP 0.9.1 method.
1140
1231
  #
1141
- # @param [String] name Exchange name
1232
+ # @param [String] name The name of the exchange. Note that LF and CR characters
1233
+ # will be stripped from the value.
1142
1234
  # @param [String,Symbol] type Exchange type, e.g. :fanout or :topic
1143
1235
  # @param [Hash] opts Exchange properties
1144
1236
  #
1145
- # @option opts [Boolean] durable (false) Should information about this echange be persisted to disk so that it
1237
+ # @option opts [Boolean] durable (false) Should information about this exchange be persisted to disk so that it
1146
1238
  # can survive broker restarts? Typically set to true for long-lived exchanges.
1147
- # @option opts [Boolean] auto_delete (false) Should this echange be deleted when it is no longer used?
1239
+ # @option opts [Boolean] auto_delete (false) Should this exchange be deleted when it is no longer used?
1148
1240
  # @option opts [Boolean] passive (false) If true, exchange will be checked for existence. If it does not
1149
1241
  # exist, {Bunny::NotFound} will be raised.
1150
1242
  #
1151
1243
  # @return [AMQ::Protocol::Exchange::DeclareOk] RabbitMQ response
1152
- # @see http://rubybunny.info/articles/echanges.html Exchanges and Publishing guide
1244
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1153
1245
  # @api public
1154
1246
  def exchange_declare(name, type, opts = {})
1155
1247
  raise_if_no_longer_open!
1156
1248
 
1249
+ # strip trailing new line and carriage returns
1250
+ # just like RabbitMQ does
1251
+ safe_name = name.gsub(/[\r\n]/, "")
1157
1252
  @connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
1158
- name,
1253
+ safe_name,
1159
1254
  type.to_s,
1160
1255
  opts.fetch(:passive, false),
1161
1256
  opts.fetch(:durable, false),
@@ -1163,7 +1258,7 @@ module Bunny
1163
1258
  opts.fetch(:internal, false),
1164
1259
  false, # nowait
1165
1260
  opts[:arguments]))
1166
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1261
+ with_continuation_timeout do
1167
1262
  @last_exchange_declare_ok = wait_on_continuations
1168
1263
  end
1169
1264
 
@@ -1188,7 +1283,7 @@ module Bunny
1188
1283
  name,
1189
1284
  opts[:if_unused],
1190
1285
  false))
1191
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1286
+ with_continuation_timeout do
1192
1287
  @last_exchange_delete_ok = wait_on_continuations
1193
1288
  end
1194
1289
 
@@ -1232,7 +1327,7 @@ module Bunny
1232
1327
  opts[:routing_key],
1233
1328
  false,
1234
1329
  opts[:arguments]))
1235
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1330
+ with_continuation_timeout do
1236
1331
  @last_exchange_bind_ok = wait_on_continuations
1237
1332
  end
1238
1333
 
@@ -1276,7 +1371,7 @@ module Bunny
1276
1371
  opts[:routing_key],
1277
1372
  false,
1278
1373
  opts[:arguments]))
1279
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1374
+ with_continuation_timeout do
1280
1375
  @last_exchange_unbind_ok = wait_on_continuations
1281
1376
  end
1282
1377
 
@@ -1304,7 +1399,7 @@ module Bunny
1304
1399
  raise_if_no_longer_open!
1305
1400
 
1306
1401
  @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
1307
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1402
+ with_continuation_timeout do
1308
1403
  @last_channel_flow_ok = wait_on_continuations
1309
1404
  end
1310
1405
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1325,7 +1420,7 @@ module Bunny
1325
1420
  raise_if_no_longer_open!
1326
1421
 
1327
1422
  @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
1328
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1423
+ with_continuation_timeout do
1329
1424
  @last_tx_select_ok = wait_on_continuations
1330
1425
  end
1331
1426
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1341,7 +1436,7 @@ module Bunny
1341
1436
  raise_if_no_longer_open!
1342
1437
 
1343
1438
  @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
1344
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1439
+ with_continuation_timeout do
1345
1440
  @last_tx_commit_ok = wait_on_continuations
1346
1441
  end
1347
1442
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1356,7 +1451,7 @@ module Bunny
1356
1451
  raise_if_no_longer_open!
1357
1452
 
1358
1453
  @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
1359
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1454
+ with_continuation_timeout do
1360
1455
  @last_tx_rollback_ok = wait_on_continuations
1361
1456
  end
1362
1457
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1403,7 +1498,7 @@ module Bunny
1403
1498
  @confirms_callback = callback
1404
1499
 
1405
1500
  @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
1406
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1501
+ with_continuation_timeout do
1407
1502
  @last_confirm_select_ok = wait_on_continuations
1408
1503
  end
1409
1504
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1501,12 +1596,15 @@ module Bunny
1501
1596
 
1502
1597
  # Recovers publisher confirms mode. Used by the Automatic Network Failure
1503
1598
  # Recovery feature.
1599
+ # Set the offset to the previous publish sequence index as the protocol will reset the index to after recovery.
1504
1600
  #
1505
1601
  # @api plugin
1506
1602
  def recover_confirm_mode
1507
1603
  if using_publisher_confirmations?
1508
- @unconfirmed_set.clear
1509
- @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
1510
1608
  confirm_select(@confirms_callback)
1511
1609
  end
1512
1610
  end
@@ -1575,7 +1673,11 @@ module Bunny
1575
1673
 
1576
1674
  # @return [String] Brief human-readable representation of the channel
1577
1675
  def to_s
1578
- "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}>"
1676
+ "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s} @open=#{open?}>"
1677
+ end
1678
+
1679
+ def inspect
1680
+ to_s
1579
1681
  end
1580
1682
 
1581
1683
 
@@ -1583,6 +1685,11 @@ module Bunny
1583
1685
  # Implementation
1584
1686
  #
1585
1687
 
1688
+ # @private
1689
+ def with_continuation_timeout(&block)
1690
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout, &block)
1691
+ end
1692
+
1586
1693
  # @private
1587
1694
  def register_consumer(consumer_tag, consumer)
1588
1695
  @consumer_mutex.synchronize do
@@ -1606,12 +1713,33 @@ module Bunny
1606
1713
  end
1607
1714
  end
1608
1715
 
1716
+ # @private
1717
+ def pending_server_named_queue_declaration?
1718
+ @pending_queue_declare_name && @pending_queue_declare_name.empty?
1719
+ end
1720
+
1721
+ # @private
1722
+ def can_accept_queue_declare_ok?(method)
1723
+ @pending_queue_declare_name == method.queue ||
1724
+ pending_server_named_queue_declaration?
1725
+ end
1726
+
1609
1727
  # @private
1610
1728
  def handle_method(method)
1611
1729
  @logger.debug { "Channel#handle_frame on channel #{@id}: #{method.inspect}" }
1612
1730
  case method
1613
1731
  when AMQ::Protocol::Queue::DeclareOk then
1614
- @continuations.push(method)
1732
+ # safeguard against late arrivals of responses and
1733
+ # so on, see ruby-amqp/bunny#558
1734
+ if can_accept_queue_declare_ok?(method)
1735
+ @continuations.push(method)
1736
+ else
1737
+ if !pending_server_named_queue_declaration?
1738
+ # this response is for an outdated/overwritten
1739
+ # queue.declare, drop 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"
1741
+ end
1742
+ end
1615
1743
  when AMQ::Protocol::Queue::DeleteOk then
1616
1744
  @continuations.push(method)
1617
1745
  when AMQ::Protocol::Queue::PurgeOk then
@@ -1732,14 +1860,16 @@ module Bunny
1732
1860
  end
1733
1861
  end
1734
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.
1735
1865
  # @private
1736
1866
  def handle_ack_or_nack(delivery_tag_before_offset, multiple, nack)
1737
- delivery_tag = delivery_tag_before_offset + @delivery_tag_offset
1738
- confirmed_range_start = multiple ? @delivery_tag_offset + 1 : delivery_tag
1739
- confirmed_range_end = delivery_tag
1740
- confirmed_range = (confirmed_range_start..confirmed_range_end)
1741
-
1742
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
+
1743
1873
  if nack
1744
1874
  @nacked_set.merge(@unconfirmed_set & confirmed_range)
1745
1875
  end
@@ -1940,7 +2070,13 @@ module Bunny
1940
2070
 
1941
2071
  # @private
1942
2072
  def raise_if_no_longer_open!
1943
- raise ChannelAlreadyClosed.new("cannot use a channel that was already closed! Channel id: #{@id}", self) if closed?
2073
+ if closed?
2074
+ if @last_channel_error
2075
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}, closed due to a server-reported channel error: #{@last_channel_error.message}", self)
2076
+ else
2077
+ raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}", self)
2078
+ end
2079
+ end
1944
2080
  end
1945
2081
 
1946
2082
  # @private
@@ -17,7 +17,9 @@ module Bunny
17
17
  #
18
18
 
19
19
  # @param [Integer] max_channel Max allowed channel id
20
- def initialize(max_channel = ((1 << 16) - 1))
20
+ def initialize(max_channel = ((1 << 11) - 1))
21
+ # channel 0 has special meaning in the protocol, so start
22
+ # allocator at 1
21
23
  @allocator = AMQ::IntAllocator.new(1, max_channel)
22
24
  @mutex = Monitor.new
23
25
  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
@@ -25,6 +25,7 @@ module Bunny
25
25
  @shutdown_conditional = ::ConditionVariable.new
26
26
  @queue = ::Queue.new
27
27
  @paused = false
28
+ @running = false
28
29
  end
29
30
 
30
31
 
@@ -69,7 +70,7 @@ module Bunny
69
70
  return if !(wait_for_workers && @shutdown_timeout && was_running)
70
71
 
71
72
  @shutdown_mutex.synchronize do
72
- @shutdown_conditional.wait(@shutdown_mutex, @shutdown_timeout)
73
+ @shutdown_conditional.wait(@shutdown_mutex, @shutdown_timeout) if busy?
73
74
  end
74
75
  end
75
76
 
@@ -32,6 +32,9 @@ module Bunny
32
32
  socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
33
33
  end
34
34
  socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
35
+ socket.instance_eval do
36
+ @__bunny_socket_eof_flag__ = false
37
+ end
35
38
  socket.extend self
36
39
  socket.options = { :host => host, :port => port }.merge(options)
37
40
  socket
@@ -24,6 +24,11 @@ module Bunny
24
24
  [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
25
25
  end
26
26
 
27
+ def initialize(*args)
28
+ super
29
+ @__bunny_socket_eof_flag__ = false
30
+ end
31
+
27
32
  # Reads given number of bytes with an optional timeout
28
33
  #
29
34
  # @param [Integer] count How many bytes to read
@@ -107,7 +112,7 @@ module Bunny
107
112
  end
108
113
 
109
114
  end
110
- rescue LoadError => le
115
+ rescue LoadError
111
116
  puts "Could not load OpenSSL"
112
117
  end
113
118
  end
@@ -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)
@@ -29,6 +29,7 @@ module Bunny
29
29
  @interval = [(period / 2) - 1, 0.4].max
30
30
 
31
31
  @thread = Thread.new(&method(:run))
32
+ @thread.report_on_exception = false if @thread.respond_to?(:report_on_exception)
32
33
  end
33
34
  end
34
35
 
@@ -63,7 +64,7 @@ module Bunny
63
64
 
64
65
  if now > (@last_activity_time + @interval)
65
66
  @logger.debug { "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}" }
66
- @transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode)
67
+ @transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode, true)
67
68
  end
68
69
  end
69
70
  end
@@ -8,6 +8,11 @@ module Bunny
8
8
  # methods found in Bunny::Socket.
9
9
  class SSLSocket < Bunny::SSLSocket
10
10
 
11
+ def initialize(*args)
12
+ super
13
+ @__bunny_socket_eof_flag__ = false
14
+ end
15
+
11
16
  # Reads given number of bytes with an optional timeout
12
17
  #
13
18
  # @param [Integer] count How many bytes to read