bunny 2.7.4 → 2.22.0

Sign up to get free protection for your applications and to get access to all the features.
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