bunny 1.3.0 → 2.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +7 -1
  4. data/.rspec +1 -3
  5. data/.travis.yml +21 -14
  6. data/CONTRIBUTING.md +132 -0
  7. data/ChangeLog.md +887 -1
  8. data/Gemfile +13 -13
  9. data/LICENSE +1 -1
  10. data/README.md +46 -60
  11. data/Rakefile +54 -0
  12. data/bunny.gemspec +5 -11
  13. data/docker-compose.yml +28 -0
  14. data/docker/Dockerfile +24 -0
  15. data/docker/apt/preferences.d/erlang +3 -0
  16. data/docker/apt/sources.list.d/bintray.rabbitmq.list +2 -0
  17. data/docker/docker-entrypoint.sh +26 -0
  18. data/docker/rabbitmq.conf +29 -0
  19. data/examples/connection/automatic_recovery_with_basic_get.rb +1 -1
  20. data/examples/connection/automatic_recovery_with_client_named_queues.rb +1 -1
  21. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +1 -1
  22. data/examples/connection/automatic_recovery_with_republishing.rb +1 -1
  23. data/examples/connection/automatic_recovery_with_server_named_queues.rb +1 -1
  24. data/examples/connection/channel_level_exception.rb +1 -9
  25. data/examples/connection/disabled_automatic_recovery.rb +1 -1
  26. data/examples/connection/heartbeat.rb +1 -1
  27. data/examples/consumers/high_and_low_priority.rb +1 -1
  28. data/examples/guides/extensions/alternate_exchange.rb +2 -0
  29. data/examples/guides/extensions/basic_nack.rb +1 -1
  30. data/examples/guides/extensions/dead_letter_exchange.rb +1 -1
  31. data/examples/guides/getting_started/hello_world.rb +2 -0
  32. data/examples/guides/getting_started/weathr.rb +2 -0
  33. data/examples/guides/queues/one_off_consumer.rb +2 -0
  34. data/examples/guides/queues/redeliveries.rb +4 -2
  35. data/lib/bunny.rb +8 -4
  36. data/lib/bunny/channel.rb +268 -153
  37. data/lib/bunny/channel_id_allocator.rb +6 -4
  38. data/lib/bunny/concurrent/continuation_queue.rb +34 -13
  39. data/lib/bunny/consumer_work_pool.rb +34 -6
  40. data/lib/bunny/cruby/socket.rb +48 -21
  41. data/lib/bunny/cruby/ssl_socket.rb +65 -4
  42. data/lib/bunny/exceptions.rb +25 -4
  43. data/lib/bunny/exchange.rb +24 -28
  44. data/lib/bunny/get_response.rb +1 -1
  45. data/lib/bunny/heartbeat_sender.rb +3 -2
  46. data/lib/bunny/jruby/socket.rb +23 -6
  47. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  48. data/lib/bunny/queue.rb +31 -22
  49. data/lib/bunny/reader_loop.rb +31 -18
  50. data/lib/bunny/session.rb +448 -159
  51. data/lib/bunny/test_kit.rb +14 -0
  52. data/lib/bunny/timeout.rb +1 -12
  53. data/lib/bunny/transport.rb +205 -98
  54. data/lib/bunny/version.rb +1 -1
  55. data/repl +1 -1
  56. data/spec/config/enabled_plugins +1 -0
  57. data/spec/config/rabbitmq.conf +13 -0
  58. data/spec/higher_level_api/integration/basic_ack_spec.rb +175 -16
  59. data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
  60. data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
  61. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
  62. data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
  63. data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
  64. data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
  65. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
  66. data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
  67. data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
  68. data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
  69. data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
  70. data/spec/higher_level_api/integration/connection_recovery_spec.rb +424 -221
  71. data/spec/higher_level_api/integration/connection_spec.rb +300 -126
  72. data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
  73. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
  74. data/spec/higher_level_api/integration/dead_lettering_spec.rb +34 -11
  75. data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
  76. data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
  77. data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
  78. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
  79. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
  80. data/spec/higher_level_api/integration/heartbeat_spec.rb +26 -8
  81. data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
  82. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
  83. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +156 -42
  84. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
  85. data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
  86. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
  87. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  88. data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
  89. data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
  90. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
  91. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
  92. data/spec/higher_level_api/integration/tls_connection_spec.rb +224 -89
  93. data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
  94. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  95. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  96. data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
  97. data/spec/issues/issue100_spec.rb +11 -11
  98. data/spec/issues/issue141_spec.rb +13 -14
  99. data/spec/issues/issue202_spec.rb +1 -1
  100. data/spec/issues/issue224_spec.rb +40 -0
  101. data/spec/issues/issue465_spec.rb +32 -0
  102. data/spec/issues/issue549_spec.rb +30 -0
  103. data/spec/issues/issue78_spec.rb +21 -24
  104. data/spec/issues/issue83_spec.rb +5 -6
  105. data/spec/issues/issue97_spec.rb +44 -45
  106. data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
  107. data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
  108. data/spec/spec_helper.rb +8 -26
  109. data/spec/stress/channel_close_stress_spec.rb +64 -0
  110. data/spec/stress/channel_open_stress_spec.rb +15 -9
  111. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
  112. data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
  113. data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
  114. data/spec/stress/connection_open_close_spec.rb +9 -9
  115. data/spec/stress/merry_go_round_spec.rb +105 -0
  116. data/spec/tls/client_key.pem +49 -25
  117. data/spec/tls/generate-server-cert.sh +8 -0
  118. data/spec/tls/server-openssl.cnf +10 -0
  119. data/spec/tls/server.csr +16 -0
  120. data/spec/tls/server_key.pem +49 -25
  121. data/spec/toxiproxy_helper.rb +28 -0
  122. data/spec/unit/bunny_spec.rb +5 -5
  123. data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
  124. data/spec/unit/concurrent/condition_spec.rb +8 -8
  125. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
  126. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
  127. data/spec/unit/exchange_recovery_spec.rb +39 -0
  128. data/spec/unit/version_delivery_tag_spec.rb +3 -3
  129. metadata +65 -47
  130. data/.ruby-version +0 -1
  131. data/lib/bunny/compatibility.rb +0 -24
  132. data/lib/bunny/system_timer.rb +0 -20
  133. data/spec/compatibility/queue_declare_spec.rb +0 -44
  134. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
  135. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  136. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  137. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  138. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  139. data/spec/stress/long_running_consumer_spec.rb +0 -83
  140. data/spec/tls/cacert.pem +0 -18
  141. data/spec/tls/client_cert.pem +0 -18
  142. data/spec/tls/server_cert.pem +0 -18
  143. data/spec/unit/system_timer_spec.rb +0 -10
@@ -1,4 +1,4 @@
1
- require "bunny/compatibility"
1
+ require 'amq/protocol'
2
2
 
3
3
  module Bunny
4
4
  # Represents AMQP 0.9.1 exchanges.
@@ -7,9 +7,6 @@ module Bunny
7
7
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
8
8
  class Exchange
9
9
 
10
- include Bunny::Compatibility
11
-
12
-
13
10
  #
14
11
  # API
15
12
  #
@@ -33,12 +30,12 @@ module Bunny
33
30
  attr_accessor :opts
34
31
 
35
32
 
36
- # The default exchange. Default exchange is a direct exchange that is predefined.
37
- # It cannot be removed. Every queue is bind to this (direct) exchange by default with
38
- # the following routing semantics: messages will be routed to the queue withe same
39
- # same name as message's routing key. In other words, if a message is published with
40
- # a routing key of "weather.usa.ca.sandiego" and there is a queue Q with this name,
41
- # that message will be routed to Q.
33
+ # The default exchange. This exchange is a direct exchange that is predefined by the broker
34
+ # and that cannot be removed. Every queue is bound to this exchange by default with
35
+ # the following routing semantics: messages will be routed to the queue with the same
36
+ # name as the message's routing key. In other words, if a message is published with
37
+ # a routing key of "weather.usa.ca.sandiego" and there is a queue with this name,
38
+ # the message will be routed to the queue.
42
39
  #
43
40
  # @param [Bunny::Channel] channel_or_connection Channel to use. {Bunny::Session} instances
44
41
  # are only supported for backwards compatibility.
@@ -46,11 +43,11 @@ module Bunny
46
43
  # @example Publishing a messages to the tasks queue
47
44
  # channel = Bunny::Channel.new(connection)
48
45
  # tasks_queue = channel.queue("tasks")
49
- # Bunny::Exchange.default(channel).publish("make clean", routing_key => "tasks")
46
+ # Bunny::Exchange.default(channel).publish("make clean", :routing_key => "tasks")
50
47
  #
51
48
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
52
49
  # @see http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.1.2.4)
53
- # @note Do not confuse default exchange with amq.direct: amq.direct is a pre-defined direct
50
+ # @note Do not confuse the default exchange with amq.direct: amq.direct is a pre-defined direct
54
51
  # exchange that doesn't have any special routing semantics.
55
52
  # @return [Exchange] An instance that corresponds to the default exchange (of type direct).
56
53
  # @api public
@@ -58,11 +55,10 @@ module Bunny
58
55
  self.new(channel_or_connection, :direct, AMQ::Protocol::EMPTY_STRING, :no_declare => true)
59
56
  end
60
57
 
61
- # @param [Bunny::Channel] channel_or_connection Channel this exchange will use. {Bunny::Session} instances are supported only for
62
- # backwards compatibility with 0.8.
63
- # @param [Symbol,String] type Exchange type
64
- # @param [String] name Exchange name
65
- # @param [Hash] opts Exchange properties
58
+ # @param [Bunny::Channel] channel Channel this exchange will use.
59
+ # @param [Symbol,String] type Exchange type
60
+ # @param [String] name Exchange name
61
+ # @param [Hash] opts Exchange properties
66
62
  #
67
63
  # @option opts [Boolean] :durable (false) Should this exchange be durable?
68
64
  # @option opts [Boolean] :auto_delete (false) Should this exchange be automatically deleted when it is no longer used?
@@ -75,10 +71,8 @@ module Bunny
75
71
  # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
76
72
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
77
73
  # @api public
78
- def initialize(channel_or_connection, type, name, opts = {})
79
- # old Bunny versions pass a connection here. In that case,
80
- # we just use default channel from it. MK.
81
- @channel = channel_from(channel_or_connection)
74
+ def initialize(channel, type, name, opts = {})
75
+ @channel = channel
82
76
  @name = name
83
77
  @type = type
84
78
  @options = self.class.add_default_options(name, opts)
@@ -88,6 +82,8 @@ module Bunny
88
82
  @internal = @options[:internal]
89
83
  @arguments = @options[:arguments]
90
84
 
85
+ @bindings = Set.new
86
+
91
87
  declare! unless opts[:no_declare] || predeclared? || (@name == AMQ::Protocol::EMPTY_STRING)
92
88
 
93
89
  @channel.register_exchange(self)
@@ -177,6 +173,7 @@ module Bunny
177
173
  # @api public
178
174
  def bind(source, opts = {})
179
175
  @channel.exchange_bind(source, self, opts)
176
+ @bindings.add(source: source, opts: opts)
180
177
 
181
178
  self
182
179
  end
@@ -197,6 +194,7 @@ module Bunny
197
194
  # @api public
198
195
  def unbind(source, opts = {})
199
196
  @channel.exchange_unbind(source, self, opts)
197
+ @bindings.delete(source: source, opts: opts)
200
198
 
201
199
  self
202
200
  end
@@ -222,8 +220,11 @@ module Bunny
222
220
 
223
221
  # @private
224
222
  def recover_from_network_failure
225
- # puts "Recovering exchange #{@name} from network failure"
226
- declare! unless predefined?
223
+ declare! unless @options[:no_declare] ||predefined?
224
+
225
+ @bindings.each do |b|
226
+ bind(b[:source], b[:opts])
227
+ end
227
228
  end
228
229
 
229
230
 
@@ -253,11 +254,6 @@ module Bunny
253
254
  @channel.exchange_declare(@name, @type, @options)
254
255
  end
255
256
 
256
- # @private
257
- def self.add_default_options(name, opts, block)
258
- { :exchange => name, :nowait => (block.nil? && !name.empty?) }.merge(opts)
259
- end
260
-
261
257
  # @private
262
258
  def self.add_default_options(name, opts)
263
259
  # :nowait is always false for Bunny
@@ -20,7 +20,7 @@ module Bunny
20
20
  attr_reader :channel
21
21
 
22
22
  # @private
23
- def initialize(get_ok, consumer, channel)
23
+ def initialize(get_ok, channel)
24
24
  @get_ok = get_ok
25
25
  @hash = {
26
26
  :delivery_tag => @get_ok.delivery_tag,
@@ -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
 
@@ -62,8 +63,8 @@ module Bunny
62
63
  now = Time.now
63
64
 
64
65
  if now > (@last_activity_time + @interval)
65
- @logger.debug "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}"
66
- @transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode)
66
+ @logger.debug { "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}" }
67
+ @transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode, true)
67
68
  end
68
69
  end
69
70
  end
@@ -5,7 +5,22 @@ module Bunny
5
5
  # TCP socket extension that uses Socket#readpartial to avoid excessive CPU
6
6
  # burn after some time. See issue #165.
7
7
  # @private
8
- class Socket < Bunny::Socket
8
+ module Socket
9
+ include Bunny::Socket
10
+
11
+ def self.open(host, port, options = {})
12
+ socket = ::Socket.tcp(host, port, nil, nil,
13
+ connect_timeout: options[:connect_timeout])
14
+ if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
15
+ socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
16
+ end
17
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true)
18
+ socket.extend self
19
+ socket.options = { :host => host, :port => port }.merge(options)
20
+ socket
21
+ rescue Errno::ETIMEDOUT
22
+ raise ClientTimeout
23
+ end
9
24
 
10
25
  # Reads given number of bytes with an optional timeout
11
26
  #
@@ -15,17 +30,17 @@ module Bunny
15
30
  # @return [String] Data read from the socket
16
31
  # @api public
17
32
  def read_fully(count, timeout = nil)
18
- return nil if @__bunny_socket_eof_flag__
19
-
20
33
  value = ''
34
+
21
35
  begin
22
36
  loop do
23
- value << readpartial(count - value.bytesize)
37
+ value << read_nonblock(count - value.bytesize)
24
38
  break if value.bytesize >= count
25
39
  end
26
40
  rescue EOFError
27
- # @eof will break Rubinius' TCPSocket implementation. MK.
28
- @__bunny_socket_eof_flag__ = true
41
+ # JRuby specific fix via https://github.com/jruby/jruby/issues/1694#issuecomment-54873532
42
+ IO.select([self], nil, nil, timeout)
43
+ retry
29
44
  rescue *READ_RETRY_EXCEPTION_CLASSES
30
45
  if IO.select([self], nil, nil, timeout)
31
46
  retry
@@ -33,8 +48,10 @@ module Bunny
33
48
  raise Timeout::Error, "IO timeout when reading #{count} bytes"
34
49
  end
35
50
  end
51
+
36
52
  value
37
53
  end # read_fully
54
+
38
55
  end
39
56
  end
40
57
  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
data/lib/bunny/queue.rb CHANGED
@@ -1,4 +1,3 @@
1
- require "bunny/compatibility"
2
1
  require "bunny/get_response"
3
2
 
4
3
  module Bunny
@@ -8,9 +7,6 @@ module Bunny
8
7
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
9
8
  class Queue
10
9
 
11
- include Bunny::Compatibility
12
-
13
-
14
10
  #
15
11
  # API
16
12
  #
@@ -22,8 +18,7 @@ module Bunny
22
18
  # @return [Hash] Options this queue was created with
23
19
  attr_reader :options
24
20
 
25
- # @param [Bunny::Channel] channel_or_connection Channel this queue will use. {Bunny::Session} instances are supported only for
26
- # backwards compatibility with 0.8.
21
+ # @param [Bunny::Channel] channel Channel this queue will use.
27
22
  # @param [String] name Queue name. Pass an empty string to make RabbitMQ generate a unique one.
28
23
  # @param [Hash] opts Queue properties
29
24
  #
@@ -36,13 +31,12 @@ module Bunny
36
31
  # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
37
32
  # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide
38
33
  # @api public
39
- def initialize(channel_or_connection, name = AMQ::Protocol::EMPTY_STRING, opts = {})
34
+ def initialize(channel, name = AMQ::Protocol::EMPTY_STRING, opts = {})
40
35
  # old Bunny versions pass a connection here. In that case,
41
36
  # we just use default channel from it. MK.
42
- @channel = channel_from(channel_or_connection)
37
+ @channel = channel
43
38
  @name = name
44
39
  @options = self.class.add_default_options(name, opts)
45
- @consumers = Hash.new
46
40
 
47
41
  @durable = @options[:durable]
48
42
  @exclusive = @options[:exclusive]
@@ -93,6 +87,15 @@ module Bunny
93
87
  @arguments
94
88
  end
95
89
 
90
+ def to_s
91
+ oid = ("0x%x" % (self.object_id << 1))
92
+ "<#{self.class.name}:#{oid} @name=\"#{name}\" channel=#{@channel.to_s} @durable=#{@durable} @auto_delete=#{@auto_delete} @exclusive=#{@exclusive} @arguments=#{@arguments}>"
93
+ end
94
+
95
+ def inspect
96
+ to_s
97
+ end
98
+
96
99
  # Binds queue to an exchange
97
100
  #
98
101
  # @param [Bunny::Exchange,String] exchange Exchange to bind to
@@ -152,9 +155,9 @@ module Bunny
152
155
  #
153
156
  # @param [Hash] opts Options
154
157
  #
158
+ # @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead
155
159
  # @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements?
156
160
  # @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue?
157
- # @option opts [Boolean] :block (false) Should the call block calling thread?
158
161
  # @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin)
159
162
  # @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let Bunny generate it for you.
160
163
  # @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions
@@ -163,17 +166,22 @@ module Bunny
163
166
  # @api public
164
167
  def subscribe(opts = {
165
168
  :consumer_tag => @channel.generate_consumer_tag,
166
- :ack => false,
169
+ :manual_ack => false,
167
170
  :exclusive => false,
168
171
  :block => false,
169
172
  :on_cancellation => nil
170
173
  }, &block)
171
174
 
175
+ unless opts[:ack].nil?
176
+ warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
177
+ opts[:manual_ack] = opts[:ack]
178
+ end
179
+
172
180
  ctag = opts.fetch(:consumer_tag, @channel.generate_consumer_tag)
173
181
  consumer = Consumer.new(@channel,
174
182
  self,
175
183
  ctag,
176
- !(opts[:ack] || opts[:manual_ack]),
184
+ !opts[:manual_ack],
177
185
  opts[:exclusive],
178
186
  opts[:arguments])
179
187
 
@@ -208,7 +216,8 @@ module Bunny
208
216
 
209
217
  # @param [Hash] opts Options
210
218
  #
211
- # @option opts [Boolean] :ack (false) Will the message be acknowledged manually?
219
+ # @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead
220
+ # @option opts [Boolean] :manual_ack (false) Will the message be acknowledged manually?
212
221
  #
213
222
  # @return [Array] Triple of delivery info, message properties and message content.
214
223
  # If the queue is empty, all three will be nils.
@@ -229,12 +238,17 @@ module Bunny
229
238
  #
230
239
  # puts "This is the message: " + payload + "\n\n"
231
240
  # conn.close
232
- def pop(opts = {:ack => false}, &block)
241
+ def pop(opts = {:manual_ack => false}, &block)
242
+ unless opts[:ack].nil?
243
+ warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead."
244
+ opts[:manual_ack] = opts[:ack]
245
+ end
246
+
233
247
  get_response, properties, content = @channel.basic_get(@name, opts)
234
248
 
235
249
  if block
236
250
  if properties
237
- di = GetResponse.new(get_response, properties, @channel)
251
+ di = GetResponse.new(get_response, @channel)
238
252
  mp = MessageProperties.new(properties)
239
253
 
240
254
  block.call(di, mp, content)
@@ -243,7 +257,7 @@ module Bunny
243
257
  end
244
258
  else
245
259
  if properties
246
- di = GetResponse.new(get_response, properties, @channel)
260
+ di = GetResponse.new(get_response, @channel)
247
261
  mp = MessageProperties.new(properties)
248
262
  [di, mp, content]
249
263
  else
@@ -326,7 +340,7 @@ module Bunny
326
340
  # TODO: inject and use logger
327
341
  # puts "Recovering queue #{@name}"
328
342
  begin
329
- declare!
343
+ declare! unless @options[:no_declare]
330
344
 
331
345
  @channel.register_queue(self)
332
346
  rescue Exception => e
@@ -358,11 +372,6 @@ module Bunny
358
372
 
359
373
  protected
360
374
 
361
- # @private
362
- def self.add_default_options(name, opts, block)
363
- { :queue => name, :nowait => (block.nil? && !name.empty?) }.merge(opts)
364
- end
365
-
366
375
  # @private
367
376
  def self.add_default_options(name, opts)
368
377
  # :nowait is always false for Bunny
@@ -9,13 +9,17 @@ module Bunny
9
9
  # @private
10
10
  class ReaderLoop
11
11
 
12
- def initialize(transport, session, session_thread)
13
- @transport = transport
14
- @session = session
15
- @session_thread = session_thread
16
- @logger = @session.logger
12
+ def initialize(transport, session, session_error_handler)
13
+ @transport = transport
14
+ @session = session
15
+ @session_error_handler = session_error_handler
16
+ @logger = @session.logger
17
17
 
18
- @mutex = Mutex.new
18
+ @mutex = Mutex.new
19
+
20
+ @stopping = false
21
+ @stopped = false
22
+ @network_is_down = false
19
23
  end
20
24
 
21
25
 
@@ -33,15 +37,17 @@ module Bunny
33
37
  begin
34
38
  break if @mutex.synchronize { @stopping || @stopped || @network_is_down }
35
39
  run_once
36
- rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError => e
40
+ rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Timeout::Error,
41
+ OpenSSL::OpenSSLError => e
37
42
  break if terminate? || @session.closing? || @session.closed?
38
43
 
39
- log_exception(e)
40
44
  @network_is_down = true
41
45
  if @session.automatically_recover?
46
+ log_exception(e, level: :warn)
42
47
  @session.handle_network_failure(e)
43
48
  else
44
- @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
49
+ log_exception(e)
50
+ @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
45
51
  end
46
52
  rescue ShutdownSignal => _
47
53
  @mutex.synchronize { @stopping = true }
@@ -52,9 +58,9 @@ module Bunny
52
58
  log_exception(e)
53
59
 
54
60
  @network_is_down = true
55
- @session_thread.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
61
+ @session_error_handler.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
56
62
  end
57
- rescue Errno::EBADF => ebadf
63
+ rescue Errno::EBADF => _ebadf
58
64
  break if terminate?
59
65
  # ignored, happens when we loop after the transport has already been closed
60
66
  @mutex.synchronize { @stopping = true }
@@ -92,11 +98,11 @@ module Bunny
92
98
  end
93
99
 
94
100
  def stopped?
95
- @mutex.synchronize { @stopped = true }
101
+ @mutex.synchronize { @stopped }
96
102
  end
97
103
 
98
104
  def stopping?
99
- @mutex.synchronize { @stopping = true }
105
+ @mutex.synchronize { @stopping }
100
106
  end
101
107
 
102
108
  def terminate_with(e)
@@ -110,7 +116,14 @@ module Bunny
110
116
  end
111
117
 
112
118
  def join
113
- @thread.join if @thread
119
+ # Thread#join can/would trigger a re-raise of an unhandled exception in this thread.
120
+ # In addition, Thread.handle_interrupt can be used by other libraries or application code
121
+ # that would make this join operation fail with an obscure exception.
122
+ # So we try to save everyone some really unpleasant debugging time by introducing
123
+ # this condition which typically would not evaluate to true anyway.
124
+ #
125
+ # See ruby-amqp/bunny#589 and ruby-amqp/bunny#590 for background.
126
+ @thread.join if @thread && @thread != Thread.current
114
127
  end
115
128
 
116
129
  def kill
@@ -122,12 +135,12 @@ module Bunny
122
135
 
123
136
  protected
124
137
 
125
- def log_exception(e)
138
+ def log_exception(e, level: :error)
126
139
  if !(io_error?(e) && (@session.closing? || @session.closed?))
127
- @logger.error "Exception in the reader loop: #{e.class.name}: #{e.message}"
128
- @logger.error "Backtrace: "
140
+ @logger.send level, "Exception in the reader loop: #{e.class.name}: #{e.message}"
141
+ @logger.send level, "Backtrace: "
129
142
  e.backtrace.each do |line|
130
- @logger.error "\t#{line}"
143
+ @logger.send level, "\t#{line}"
131
144
  end
132
145
  end
133
146
  end