bunny 2.4.0 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 790cbbf1db8e302bc827c5bea5f7402246666232
4
- data.tar.gz: 5db72065394300e1836777389d4cefc96f2213b2
3
+ metadata.gz: 6f628f0ace95911e5d5ce48817389f79a3a68415
4
+ data.tar.gz: a4f867d1fbe68457f311a0b3691a644c882cc1e4
5
5
  SHA512:
6
- metadata.gz: a209a0ee2b9d6bb19616d647f7070dbf6834aff8faa3136e1ae452fa0aae5980c9bc4bffedb6a8f4062f92b3469580936c3c94deca3d386120543ae11f737538
7
- data.tar.gz: ca0250521c37790299f627edf42db7598ddfe21d1cfd749f84745f5003c160d3c12fa52dbbb3c899aa709125f715bfe55c53697ef2bf05b0db89ec98b5ad6934
6
+ metadata.gz: 792e2d8e367bca866fa48af83d640b75feb401da8a10711852df09d436ca614cdf0a7c52a72fe2a890cb627991639b771cd0ed6fa3b912bc235206c00da219ec
7
+ data.tar.gz: 8a3fa96ab3e6103513f1df439c37f8d4ce6688c9dba2c40e7fadbec265bf1cf5e2abd438b16f7e954ff6c1479130ecc0253023c8e6d50248a199ce2a13ab8098
@@ -55,7 +55,7 @@ it by pressing CTRL+C.
55
55
  ### Running Test Suites
56
56
 
57
57
  Prior to running the tests, configure the RabbitMQ permissions
58
- by running `./bin/ci/before_script`. The script uses `rabbitmqctl` and `rabbitmq-plugins`
58
+ by running `./bin/ci/before_build`. The script uses `rabbitmqctl` and `rabbitmq-plugins`
59
59
  to set up RabbitMQ in a way that Bunny test suites expect. Two environment variables,
60
60
  `RABBITMQCTL` and `RABBITMQ_PLUGINS`, are available to control what `rabbitmqctl` and
61
61
  `rabbitmq-plugins` commands will be used. By default they are taken from `PATH`
@@ -1,4 +1,9 @@
1
- ## Changes between Bunny 2.3.0 and 2.4.0 (unreleased)
1
+ ## Changes between Bunny 2.4.0 and 2.5.0 (unreleased)
2
+
3
+ No changes yet.
4
+
5
+
6
+ ## Changes between Bunny 2.3.0 and 2.4.0 (June 11th, 2016)
2
7
 
3
8
  ### Unconfirmed Delivery Tag Set Reset on Network Recovery
4
9
 
@@ -1783,6 +1783,8 @@ module Bunny
1783
1783
 
1784
1784
  # @private
1785
1785
  def wait_on_confirms_continuations
1786
+ raise_if_no_longer_open!
1787
+
1786
1788
  if @connection.threaded
1787
1789
  t = Thread.current
1788
1790
  @threads_waiting_on_confirms_continuations << t
@@ -1,3 +1,5 @@
1
+ require 'amq/protocol'
2
+
1
3
  module Bunny
2
4
  # Represents AMQP 0.9.1 exchanges.
3
5
  #
@@ -80,6 +82,8 @@ module Bunny
80
82
  @internal = @options[:internal]
81
83
  @arguments = @options[:arguments]
82
84
 
85
+ @bindings = Set.new
86
+
83
87
  declare! unless opts[:no_declare] || predeclared? || (@name == AMQ::Protocol::EMPTY_STRING)
84
88
 
85
89
  @channel.register_exchange(self)
@@ -169,6 +173,7 @@ module Bunny
169
173
  # @api public
170
174
  def bind(source, opts = {})
171
175
  @channel.exchange_bind(source, self, opts)
176
+ @bindings.add(source: source, opts: opts)
172
177
 
173
178
  self
174
179
  end
@@ -189,6 +194,7 @@ module Bunny
189
194
  # @api public
190
195
  def unbind(source, opts = {})
191
196
  @channel.exchange_unbind(source, self, opts)
197
+ @bindings.delete(source: source, opts: opts)
192
198
 
193
199
  self
194
200
  end
@@ -214,8 +220,11 @@ module Bunny
214
220
 
215
221
  # @private
216
222
  def recover_from_network_failure
217
- # puts "Recovering exchange #{@name} from network failure"
218
- declare! unless predefined?
223
+ declare! unless @options[:no_declare] ||predefined?
224
+
225
+ @bindings.each do |b|
226
+ bind(b[:source], b[:opts])
227
+ end
219
228
  end
220
229
 
221
230
 
@@ -341,7 +341,7 @@ module Bunny
341
341
  # TODO: inject and use logger
342
342
  # puts "Recovering queue #{@name}"
343
343
  begin
344
- declare!
344
+ declare! unless @options[:no_declare]
345
345
 
346
346
  @channel.register_queue(self)
347
347
  rescue Exception => e
@@ -108,6 +108,7 @@ module Bunny
108
108
  # @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem)
109
109
  # @option connection_string_or_opts [Array<String>] :tls_ca_certificates Array of paths to TLS/SSL CA files (.pem), by default detected from OpenSSL configuration
110
110
  # @option connection_string_or_opts [String] :verify_peer (true) Whether TLS peer verification should be performed
111
+ # @option connection_string_or_opts [Keyword] :tls_version (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2)
111
112
  # @option connection_string_or_opts [Integer] :continuation_timeout (15000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds.
112
113
  # @option connection_string_or_opts [Integer] :connection_timeout (5) Timeout in seconds for connecting to the server.
113
114
  # @option connection_string_or_opts [Proc] :hosts_shuffle_strategy A Proc that reorders a list of host strings, defaults to Array#shuffle
@@ -668,30 +669,28 @@ module Bunny
668
669
 
669
670
  # @private
670
671
  def recover_from_network_failure
671
- begin
672
- sleep @network_recovery_interval
673
- @logger.debug "About to start connection recovery..."
672
+ sleep @network_recovery_interval
673
+ @logger.debug "About to start connection recovery..."
674
674
 
675
- self.initialize_transport
675
+ self.initialize_transport
676
676
 
677
- @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
678
- self.start
677
+ @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
678
+ self.start
679
679
 
680
- if open?
681
- @recovering_from_network_failure = false
680
+ if open?
681
+ @recovering_from_network_failure = false
682
682
 
683
- recover_channels
684
- end
685
- rescue HostListDepleted
686
- reset_address_index
687
- retry
688
- rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e
689
- @logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds"
690
- sleep @network_recovery_interval
691
- if should_retry_recovery?
692
- @recovery_attempts -= 1 if @recovery_attempts
693
- retry if recoverable_network_failure?(e)
694
- end
683
+ recover_channels
684
+ end
685
+ rescue HostListDepleted
686
+ reset_address_index
687
+ retry
688
+ rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e
689
+ @logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds"
690
+ sleep @network_recovery_interval
691
+ if should_retry_recovery?
692
+ @recovery_attempts -= 1 if @recovery_attempts
693
+ retry if recoverable_network_failure?(e)
695
694
  end
696
695
  end
697
696
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "2.4.0"
5
+ VERSION = "2.5.0"
6
6
  end
@@ -1,403 +1,421 @@
1
1
  require "spec_helper"
2
2
  require "rabbitmq/http/client"
3
3
 
4
- unless ENV["CI"]
5
- describe "Connection recovery" do
6
- let(:connection) { }
7
- let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") }
8
-
9
- def close_all_connections!
10
- http_client.list_connections.each do |conn_info|
11
- begin
12
- http_client.close_connection(conn_info.name)
13
- rescue Bunny::ConnectionForced
14
- # This is not a problem, but the specs intermittently believe it is.
15
- end
16
- end
4
+ describe "Connection recovery" do
5
+ let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") }
6
+ let(:logger) { Logger.new($stderr).tap {|logger| logger.level = Logger::FATAL} }
7
+ let(:recovery_interval) { 0.2 }
8
+
9
+ it "reconnects after grace period" do
10
+ with_open do |c|
11
+ close_all_connections!
12
+ wait_on_loss_and_recovery_of { connections.any? }
17
13
  end
14
+ end
18
15
 
19
- def wait_for_recovery
20
- sleep 1.5
16
+ it "reconnects after grace period (with multiple hosts)" do
17
+ with_open_multi_host do |c|
18
+ close_all_connections!
19
+ wait_on_loss_and_recovery_of { connections.any? }
21
20
  end
21
+ end
22
22
 
23
- def with_open(c = Bunny.new(:network_recovery_interval => 0.2, :recover_from_connection_close => true), &block)
24
- begin
25
- c.start
26
- block.call(c)
27
- ensure
28
- c.close
29
- end
23
+ it "reconnects after grace period (with multiple hosts, including a broken one)" do
24
+ with_open_multi_broken_host do |c|
25
+ close_all_connections!
26
+ wait_on_loss_and_recovery_of { connections.any? }
30
27
  end
28
+ end
31
29
 
32
- def with_open_multi_host( c = Bunny.new( :hosts => ["127.0.0.1", "localhost"],
33
- :network_recovery_interval => 0.2,
34
- :recover_from_connection_close => true), &block)
35
- begin
36
- c.start
37
- block.call(c)
38
- ensure
39
- c.close
40
- end
30
+ it "recovers channels" do
31
+ with_open do |c|
32
+ ch1 = c.create_channel
33
+ ch2 = c.create_channel
34
+ close_all_connections!
35
+ poll_until { channels.count.zero? }
36
+ poll_until { channels.count == 2 }
37
+ expect(ch1).to be_open
38
+ expect(ch2).to be_open
41
39
  end
40
+ end
42
41
 
43
- def with_open_multi_broken_host( c = Bunny.new( :hosts => ["broken", "127.0.0.1", "localhost"],
44
- :hosts_shuffle_strategy => Proc.new { |hosts| hosts }, # We do not shuffle for these tests so we always hit the broken host
45
- :network_recovery_interval => 0.2,
46
- :recover_from_connection_close => true), &block)
47
- begin
48
- c.start
49
- block.call(c)
50
- ensure
51
- c.close
52
- end
42
+ it "recovers channels (with multiple hosts)" do
43
+ with_open_multi_host do |c|
44
+ ch1 = c.create_channel
45
+ ch2 = c.create_channel
46
+ close_all_connections!
47
+ poll_until { channels.count.zero? }
48
+ poll_until { channels.count == 2 }
49
+ expect(ch1).to be_open
50
+ expect(ch2).to be_open
53
51
  end
52
+ end
54
53
 
55
- def with_recovery_attempts_limited_to(attempts = 3, &block)
56
- c = Bunny.new(:recover_from_connection_close => true, :network_recovery_interval => 0.2, :recovery_attempts => attempts)
57
- begin
58
- c.start
59
- block.call(c)
60
- ensure
61
- c.close
62
- end
54
+ it "recovers channels (with multiple hosts, including a broken one)" do
55
+ with_open_multi_broken_host do |c|
56
+ ch1 = c.create_channel
57
+ ch2 = c.create_channel
58
+ close_all_connections!
59
+ poll_until { channels.count.zero? }
60
+ poll_until { channels.count == 2 }
61
+ expect(ch1).to be_open
62
+ expect(ch2).to be_open
63
63
  end
64
+ end
64
65
 
65
- def ensure_queue_recovery(ch, q)
66
- ch.confirm_select
67
- q.purge
68
- x = ch.default_exchange
69
- x.publish("msg", :routing_key => q.name)
70
- ch.wait_for_confirms
71
- sleep 0.5
72
- expect(q.message_count).to eq 1
73
- q.purge
66
+ it "recovers basic.qos prefetch setting" do
67
+ with_open do |c|
68
+ ch = c.create_channel
69
+ ch.prefetch(11)
70
+ expect(ch.prefetch_count).to eq 11
71
+ expect(ch.prefetch_global).to be false
72
+ close_all_connections!
73
+ wait_on_loss_and_recovery_of { connections.any? }
74
+ expect(ch).to be_open
75
+ expect(ch.prefetch_count).to eq 11
76
+ expect(ch.prefetch_global).to be false
74
77
  end
78
+ end
75
79
 
76
- def ensure_queue_binding_recovery(ch, x, q, routing_key = "")
77
- ch.confirm_select
78
- q.purge
79
- x.publish("msg", :routing_key => routing_key)
80
- ch.wait_for_confirms
81
- sleep 0.5
82
- expect(q.message_count).to eq 1
83
- q.purge
80
+ it "recovers basic.qos prefetch global setting" do
81
+ with_open do |c|
82
+ ch = c.create_channel
83
+ ch.prefetch(42, true)
84
+ expect(ch.prefetch_count).to eq 42
85
+ expect(ch.prefetch_global).to be true
86
+ close_all_connections!
87
+ wait_on_loss_and_recovery_of { connections.any? }
88
+ expect(ch).to be_open
89
+ expect(ch.prefetch_count).to eq 42
90
+ expect(ch.prefetch_global).to be true
84
91
  end
92
+ end
85
93
 
86
- def ensure_exchange_binding_recovery(ch, source, destination, routing_key = "")
94
+ it "recovers publisher confirms setting" do
95
+ with_open do |c|
96
+ ch = c.create_channel
87
97
  ch.confirm_select
88
- q = ch.queue("", :exclusive => true)
89
- q.bind(destination, :routing_key => routing_key)
98
+ expect(ch).to be_using_publisher_confirms
99
+ close_all_connections!
100
+ wait_on_loss_and_recovery_of { connections.any? }
101
+ expect(ch).to be_open
102
+ expect(ch).to be_using_publisher_confirms
103
+ end
104
+ end
90
105
 
91
- source.publish("msg", :routing_key => routing_key)
92
- ch.wait_for_confirms
93
- sleep 0.5
94
- expect(q.message_count).to eq 1
95
- q.delete
106
+ it "recovers transactionality setting" do
107
+ with_open do |c|
108
+ ch = c.create_channel
109
+ ch.tx_select
110
+ expect(ch).to be_using_tx
111
+ close_all_connections!
112
+ wait_on_loss_and_recovery_of { connections.any? }
113
+ expect(ch).to be_open
114
+ expect(ch).to be_using_tx
96
115
  end
116
+ end
97
117
 
98
- #
99
- # Examples
100
- #
118
+ it "recovers client-named queues" do
119
+ with_open do |c|
120
+ ch = c.create_channel
121
+ q = ch.queue("bunny.tests.recovery.client-named#{rand}")
122
+ close_all_connections!
123
+ wait_on_loss_and_recovery_of { connections.any? }
124
+ expect(ch).to be_open
125
+ ensure_queue_recovery(ch, q)
126
+ q.delete
127
+ end
128
+ end
101
129
 
102
- it "reconnects after grace period" do
103
- with_open do |c|
104
- close_all_connections!
105
- sleep 0.1
106
- expect(c).not_to be_open
130
+ # a very simplistic test for queues inspired by #412
131
+ it "recovers client-named queues declared with passive = true" do
132
+ with_open do |c|
133
+ ch = c.create_channel
134
+ ch2 = c.create_channel
107
135
 
108
- wait_for_recovery
109
- expect(c).to be_open
110
- end
111
- end
136
+ n = rand
137
+ s = "bunny.tests.recovery.client-named#{n}"
112
138
 
113
- it "reconnects after grace period (with multiple hosts)" do
114
- with_open_multi_host do |c|
115
- close_all_connections!
116
- sleep 0.1
117
- expect(c).not_to be_open
139
+ q = ch.queue(s)
140
+ q2 = ch2.queue(s, no_declare: true)
118
141
 
119
- wait_for_recovery
120
- expect(c).to be_open
121
- end
142
+ close_all_connections!
143
+ wait_on_loss_and_recovery_of { connections.any? }
144
+ expect(ch).to be_open
145
+ ensure_queue_recovery(ch, q)
146
+ q.delete
122
147
  end
148
+ end
123
149
 
124
- it "reconnects after grace period (with multiple hosts, including a broken one)" do
125
- with_open_multi_broken_host do |c|
126
- close_all_connections!
127
- sleep 0.1
128
- expect(c).not_to be_open
129
150
 
130
- wait_for_recovery
131
- expect(c).to be_open
132
- end
151
+ it "recovers server-named queues" do
152
+ with_open do |c|
153
+ ch = c.create_channel
154
+ q = ch.queue("", :exclusive => true)
155
+ close_all_connections!
156
+ wait_on_loss_and_recovery_of { connections.any? }
157
+ expect(ch).to be_open
158
+ ensure_queue_recovery(ch, q)
133
159
  end
160
+ end
134
161
 
135
- it "recovers channels" do
136
- with_open do |c|
137
- ch1 = c.create_channel
138
- ch2 = c.create_channel
139
- close_all_connections!
140
- sleep 0.1
141
- expect(c).not_to be_open
142
-
143
- wait_for_recovery
144
- expect(ch1).to be_open
145
- expect(ch2).to be_open
146
- end
162
+ it "recovers queue bindings" do
163
+ with_open do |c|
164
+ ch = c.create_channel
165
+ x = ch.fanout("amq.fanout")
166
+ q = ch.queue("", :exclusive => true)
167
+ q.bind(x)
168
+ close_all_connections!
169
+ wait_on_loss_and_recovery_of { connections.any? }
170
+ expect(ch).to be_open
171
+ ensure_queue_binding_recovery(ch, x, q)
147
172
  end
173
+ end
148
174
 
149
- it "recovers channels (with multiple hosts)" do
150
- with_open_multi_host do |c|
151
- ch1 = c.create_channel
152
- ch2 = c.create_channel
153
- close_all_connections!
154
- sleep 0.1
155
- expect(c).not_to be_open
156
-
157
- wait_for_recovery
158
- expect(ch1).to be_open
159
- expect(ch2).to be_open
160
- end
161
- end
175
+ it "recovers exchanges and their bindings" do
176
+ with_open do |c|
177
+ ch = c.create_channel
178
+ source = ch.fanout("source.exchange.recovery.example", auto_delete: true)
179
+ destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true)
162
180
 
163
- it "recovers channels (with multiple hosts, including a broken one)" do
164
- with_open_multi_broken_host do |c|
165
- ch1 = c.create_channel
166
- ch2 = c.create_channel
167
- close_all_connections!
168
- sleep 0.1
169
- expect(c).not_to be_open
170
-
171
- wait_for_recovery
172
- expect(ch1).to be_open
173
- expect(ch2).to be_open
174
- end
175
- end
181
+ destination.bind(source)
176
182
 
177
- it "recovers basic.qos prefetch setting" do
178
- with_open do |c|
179
- ch = c.create_channel
180
- ch.prefetch(11)
181
- expect(ch.prefetch_count).to eq 11
182
- expect(ch.prefetch_global).to be false
183
- close_all_connections!
184
- sleep 0.1
185
- expect(c).not_to be_open
186
-
187
- wait_for_recovery
188
- expect(ch).to be_open
189
- expect(ch.prefetch_count).to eq 11
190
- expect(ch.prefetch_global).to be false
191
- end
192
- end
183
+ # Exchanges won't get auto-deleted on connection loss unless they have
184
+ # had an exclusive queue bound to them.
185
+ dst_queue = ch.queue("", exclusive: true)
186
+ dst_queue.bind(destination, routing_key: "")
193
187
 
194
- it "recovers basic.qos prefetch global setting" do
195
- with_open do |c|
196
- ch = c.create_channel
197
- ch.prefetch(42, true)
198
- expect(ch.prefetch_count).to eq 42
199
- expect(ch.prefetch_global).to be true
200
- close_all_connections!
201
- sleep 0.1
202
- expect(c).not_to be_open
203
-
204
- wait_for_recovery
205
- expect(ch).to be_open
206
- expect(ch.prefetch_count).to eq 42
207
- expect(ch.prefetch_global).to be true
208
- end
188
+ src_queue = ch.queue("", exclusive: true)
189
+ src_queue.bind(source, routing_key: "")
190
+
191
+ close_all_connections!
192
+
193
+ wait_on_loss_and_recovery_of { exchange_names_in_vhost("/").include?(source.name) }
194
+
195
+ ch.confirm_select
196
+
197
+ source.publish("msg", routing_key: "")
198
+ ch.wait_for_confirms
199
+ expect(dst_queue.message_count).to eq 1
209
200
  end
201
+ end
210
202
 
211
- it "recovers publisher confirms setting" do
212
- with_open do |c|
213
- ch = c.create_channel
214
- ch.confirm_select
215
- expect(ch).to be_using_publisher_confirms
216
- close_all_connections!
217
- sleep 0.1
218
- expect(c).not_to be_open
219
-
220
- wait_for_recovery
221
- expect(ch).to be_open
222
- expect(ch).to be_using_publisher_confirms
223
- end
203
+ # this is a simplistic test that primarily execises the code path from #412
204
+ it "recovers exchanges that were declared with passive = true" do
205
+ with_open do |c|
206
+ ch = c.create_channel
207
+ ch2 = c.create_channel
208
+ source = ch.fanout("source.exchange.recovery.example", auto_delete: true)
209
+ destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true)
210
+
211
+ source2 = ch2.fanout("source.exchange.recovery.example", no_declare: true)
212
+ destination2 = ch2.fanout("destination.exchange.recovery.example", no_declare: true)
213
+
214
+ destination.bind(source)
215
+
216
+ # Exchanges won't get auto-deleted on connection loss unless they have
217
+ # had an exclusive queue bound to them.
218
+ dst_queue = ch.queue("", exclusive: true)
219
+ dst_queue.bind(destination, routing_key: "")
220
+
221
+ src_queue = ch.queue("", exclusive: true)
222
+ src_queue.bind(source, routing_key: "")
223
+
224
+ close_all_connections!
225
+
226
+ wait_on_loss_and_recovery_of { exchange_names_in_vhost("/").include?(source.name) }
227
+
228
+ ch2.confirm_select
229
+
230
+ source2.publish("msg", routing_key: "")
231
+ ch2.wait_for_confirms
232
+ expect(dst_queue.message_count).to eq 1
224
233
  end
234
+ end
225
235
 
226
- it "recovers transactionality setting" do
227
- with_open do |c|
228
- ch = c.create_channel
229
- ch.tx_select
230
- expect(ch).to be_using_tx
231
- close_all_connections!
232
- sleep 0.1
233
- expect(c).not_to be_open
234
-
235
- wait_for_recovery
236
- expect(ch).to be_open
237
- expect(ch).to be_using_tx
238
- end
236
+ it "recovers allocated channel ids" do
237
+ with_open do |c|
238
+ q = "queue#{Time.now.to_i}"
239
+ 10.times { c.create_channel }
240
+ expect(c.queue_exists?(q)).to eq false
241
+ close_all_connections!
242
+ wait_on_loss_and_recovery_of { channels.any? }
243
+ # make sure the connection isn't closed shortly after
244
+ # due to "second 'channel.open' seen". MK.
245
+ expect(c).to be_open
246
+ sleep 0.1
247
+ expect(c).to be_open
248
+ sleep 0.1
249
+ expect(c).to be_open
239
250
  end
251
+ end
252
+
253
+ it "recovers consumers" do
254
+ with_open do |c|
255
+ delivered = false
240
256
 
241
- it "recovers client-named queues" do
242
- with_open do |c|
243
- ch = c.create_channel
244
- q = ch.queue("bunny.tests.recovery.client-named#{rand}")
245
- close_all_connections!
246
- sleep 0.1
247
- expect(c).not_to be_open
248
-
249
- wait_for_recovery
250
- expect(ch).to be_open
251
- ensure_queue_recovery(ch, q)
252
- q.delete
257
+ ch = c.create_channel
258
+ q = ch.queue("", :exclusive => true)
259
+ q.subscribe do |_, _, _|
260
+ delivered = true
253
261
  end
262
+ close_all_connections!
263
+ wait_on_loss_and_recovery_of { connections.any? }
264
+ expect(ch).to be_open
265
+
266
+ q.publish("")
267
+
268
+ poll_until { delivered }
254
269
  end
270
+ end
255
271
 
272
+ it "recovers all consumers" do
273
+ n = 1024
256
274
 
257
- it "recovers server-named queues" do
258
- with_open do |c|
259
- ch = c.create_channel
260
- q = ch.queue("", :exclusive => true)
261
- close_all_connections!
262
- sleep 0.1
263
- expect(c).not_to be_open
275
+ with_open do |c|
276
+ ch = c.create_channel
277
+ q = ch.queue("", :exclusive => true)
278
+ n.times { q.subscribe { |_, _, _| } }
279
+ close_all_connections!
280
+ wait_on_loss_and_recovery_of { connections.any? }
281
+ expect(ch).to be_open
282
+ sleep 0.5
264
283
 
265
- wait_for_recovery
266
- expect(ch).to be_open
267
- ensure_queue_recovery(ch, q)
268
- end
284
+ expect(q.consumer_count).to eq n
269
285
  end
286
+ end
270
287
 
271
- it "recovers queue bindings" do
272
- with_open do |c|
273
- ch = c.create_channel
274
- x = ch.fanout("amq.fanout")
275
- q = ch.queue("", :exclusive => true)
276
- q.bind(x)
277
- close_all_connections!
278
- sleep 0.1
279
- expect(c).not_to be_open
280
-
281
- wait_for_recovery
282
- expect(ch).to be_open
283
- ensure_queue_binding_recovery(ch, x, q)
284
- end
285
- end
288
+ it "recovers all queues" do
289
+ n = 256
286
290
 
287
- it "recovers exchange bindings" do
288
- with_open do |c|
289
- ch = c.create_channel
290
- x = ch.fanout("amq.fanout")
291
- x2 = ch.fanout("bunny.tests.recovery.fanout")
292
- x2.bind(x)
293
- close_all_connections!
294
- sleep 0.1
295
- expect(c).not_to be_open
296
-
297
- wait_for_recovery
298
- expect(ch).to be_open
299
- ensure_exchange_binding_recovery(ch, x, x2)
291
+ qs = []
292
+
293
+ with_open do |c|
294
+ ch = c.create_channel
295
+
296
+ n.times do
297
+ qs << ch.queue("", :exclusive => true)
300
298
  end
301
- end
299
+ close_all_connections!
300
+ wait_on_loss_and_recovery_of { queue_names.include?(qs.first.name) }
301
+ sleep 0.5
302
+ expect(ch).to be_open
302
303
 
303
- it "recovers allocated channel ids" do
304
- with_open do |c|
305
- q = "queue#{Time.now.to_i}"
306
- 10.times { c.create_channel }
307
- expect(c.queue_exists?(q)).to eq false
308
- close_all_connections!
309
- sleep 0.1
310
- expect(c).not_to be_open
311
-
312
- wait_for_recovery
313
- expect(c.queue_exists?(q)).to eq false
314
- # make sure the connection isn't closed shortly after
315
- # due to "second 'channel.open' seen". MK.
316
- expect(c).to be_open
317
- sleep 0.1
318
- expect(c).to be_open
319
- sleep 0.1
320
- expect(c).to be_open
304
+ qs.each do |q|
305
+ ch.queue_declare(q.name, :passive => true)
321
306
  end
322
307
  end
308
+ end
323
309
 
324
- it "recovers consumers" do
325
- with_open do |c|
326
- delivered = false
327
-
328
- ch = c.create_channel
329
- q = ch.queue("", :exclusive => true)
330
- q.subscribe do |_, _, _|
331
- delivered = true
332
- end
333
- close_all_connections!
334
- sleep 0.1
335
- expect(c).not_to be_open
336
-
337
- wait_for_recovery
338
- expect(ch).to be_open
339
-
340
- q.publish("")
341
- sleep 0.5
342
- expect(delivered).to eq true
343
- end
310
+ it "tries to recover for a given number of attempts" do
311
+ pending "Need a fix for https://github.com/ruby-amqp/bunny/issues/408"
312
+ with_recovery_attempts_limited_to(2) do |c|
313
+ close_all_connections!
314
+ wait_on_loss_and_recovery_of { connections.any? }
315
+
316
+ close_all_connections!
317
+ wait_on_loss_and_recovery_of { connections.any? }
318
+
319
+ close_all_connections!
320
+ sleep(recovery_interval + 0.5)
321
+ expect(connections).to be_empty
344
322
  end
323
+ end
345
324
 
346
- it "recovers all consumers" do
347
- n = 1024
348
-
349
- with_open do |c|
350
- ch = c.create_channel
351
- q = ch.queue("", :exclusive => true)
352
- n.times do
353
- q.subscribe do |_, _, _|
354
- delivered = true
355
- end
356
- end
357
- close_all_connections!
358
- sleep 0.1
359
- expect(c).not_to be_open
360
-
361
- wait_for_recovery
362
- sleep 1
363
- expect(ch).to be_open
364
-
365
- expect(q.consumer_count).to eq n
366
- end
325
+ def exchange_names_in_vhost(vhost)
326
+ http_client.list_exchanges(vhost).map {|e| e["name"]}
327
+ end
328
+
329
+ def connections
330
+ http_client.list_connections
331
+ end
332
+
333
+ def channels
334
+ http_client.list_channels
335
+ end
336
+
337
+ def queue_names
338
+ http_client.list_queues.map {|q| q["name"]}
339
+ end
340
+
341
+ def close_all_connections!
342
+ connections.each do |conn_info|
343
+ close_ignoring_permitted_exceptions(conn_info.name)
367
344
  end
345
+ end
368
346
 
369
- it "recovers all queues" do
370
- n = 256
347
+ def close_ignoring_permitted_exceptions(connection_name)
348
+ http_client.close_connection(connection_name)
349
+ rescue Bunny::ConnectionForced
350
+ end
371
351
 
372
- qs = []
352
+ def wait_on_loss_and_recovery_of(&probe)
353
+ poll_while &probe
354
+ poll_until &probe
355
+ end
373
356
 
374
- with_open do |c|
375
- ch = c.create_channel
357
+ def poll_while(&probe)
358
+ Timeout::timeout(10) {
359
+ sleep 0.1 while probe[]
360
+ }
361
+ end
376
362
 
377
- n.times do
378
- qs << ch.queue("", :exclusive => true)
379
- end
380
- close_all_connections!
381
- sleep 0.1
382
- expect(c).not_to be_open
363
+ def poll_until(&probe)
364
+ Timeout::timeout(10) {
365
+ sleep 0.1 until probe[]
366
+ }
367
+ end
383
368
 
384
- wait_for_recovery
385
- sleep 1
386
- expect(ch).to be_open
369
+ def with_open(c = Bunny.new(network_recovery_interval: recovery_interval,
370
+ recover_from_connection_close: true,
371
+ logger: logger), &block)
372
+ c.start
373
+ block.call(c)
374
+ ensure
375
+ c.close
376
+ end
387
377
 
388
- qs.each do |q|
389
- ch.queue_declare(q.name, :passive => true)
390
- end
391
- end
392
- end
378
+ def with_open_multi_host(&block)
379
+ c = Bunny.new(hosts: ["127.0.0.1", "localhost"],
380
+ network_recovery_interval: recovery_interval,
381
+ recover_from_connection_close: true,
382
+ logger: logger)
383
+ with_open(c, &block)
384
+ end
393
385
 
394
- it "tries to recover for a given number of attempts" do
395
- with_recovery_attempts_limited_to(2) do |c|
396
- close_all_connections!
397
- expect(c).to receive(:start).exactly(2).times.and_raise(Bunny::TCPConnectionFailed.new("test"))
386
+ def with_open_multi_broken_host(&block)
387
+ c = Bunny.new(hosts: ["broken", "127.0.0.1", "localhost"],
388
+ hosts_shuffle_strategy: Proc.new { |hosts| hosts }, # We do not shuffle for these tests so we always hit the broken host
389
+ network_recovery_interval: recovery_interval,
390
+ recover_from_connection_close: true,
391
+ logger: logger)
392
+ with_open(c, &block)
393
+ end
398
394
 
399
- wait_for_recovery
400
- end
401
- end
395
+ def with_recovery_attempts_limited_to(attempts = 3, &block)
396
+ c = Bunny.new(recover_from_connection_close: true,
397
+ network_recovery_interval: recovery_interval,
398
+ recovery_attempts: attempts,
399
+ logger: logger)
400
+ with_open(c, &block)
401
+ end
402
+
403
+ def ensure_queue_recovery(ch, q)
404
+ ch.confirm_select
405
+ q.purge
406
+ x = ch.default_exchange
407
+ x.publish("msg", routing_key: q.name)
408
+ ch.wait_for_confirms
409
+ expect(q.message_count).to eq 1
410
+ q.purge
411
+ end
412
+
413
+ def ensure_queue_binding_recovery(ch, x, q, routing_key = "")
414
+ ch.confirm_select
415
+ q.purge
416
+ x.publish("msg", routing_key: routing_key)
417
+ ch.wait_for_confirms
418
+ expect(q.message_count).to eq 1
419
+ q.purge
402
420
  end
403
421
  end
@@ -60,6 +60,18 @@ describe Bunny::Channel do
60
60
  }.not_to raise_error
61
61
 
62
62
  end
63
+
64
+ it "raises an error when called on a closed channel" do
65
+ ch = connection.create_channel
66
+
67
+ ch.confirm_select
68
+
69
+ ch.close
70
+
71
+ expect {
72
+ ch.wait_for_confirms
73
+ }.to raise_error(Bunny::ChannelAlreadyClosed)
74
+ end
63
75
  end
64
76
 
65
77
  context "when some of the messages get nacked" do
@@ -0,0 +1,39 @@
1
+ require_relative '../../lib/bunny/channel'
2
+ require_relative '../../lib/bunny/exchange'
3
+
4
+ module Bunny
5
+ describe Exchange do
6
+ context "recovery" do
7
+ it "recovers exchange bindings, unless already unbound" do
8
+ ch = instance_double(Bunny::Channel,
9
+ exchange_declare: nil,
10
+ register_exchange: nil)
11
+ src1 = Exchange.new(ch, "direct", "src1")
12
+ src2 = Exchange.new(ch, "direct", "src2")
13
+ src3 = Exchange.new(ch, "direct", "src3")
14
+ dst = Exchange.new(ch, "direct", "dst")
15
+
16
+ original_binds_count = 5
17
+ expected_rebinds_count = 3
18
+ expected_total_binds = original_binds_count + expected_rebinds_count
19
+ allow(ch).to receive(:exchange_bind).exactly(expected_total_binds).times
20
+
21
+ dst.bind(src1, routing_key: "abc")
22
+ dst.bind(src2, routing_key: "def")
23
+ dst.bind(src2, routing_key: "ghi")
24
+ dst.bind(src3, routing_key: "jkl")
25
+ dst.bind(src3, routing_key: "jkl", arguments: {"key" => "value"})
26
+
27
+ allow(ch).to receive(:exchange_unbind).twice
28
+ dst.unbind(src2, routing_key: "def")
29
+ dst.unbind(src3, routing_key: "jkl", arguments: {"key" => "value"})
30
+
31
+ expect(ch).to receive(:exchange_bind).with(src1, dst, routing_key: "abc")
32
+ expect(ch).to receive(:exchange_bind).with(src2, dst, routing_key: "ghi")
33
+ expect(ch).to receive(:exchange_bind).with(src3, dst, routing_key: "jkl")
34
+
35
+ dst.recover_from_network_failure
36
+ end
37
+ end
38
+ end
39
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2016-06-11 00:00:00.000000000 Z
15
+ date: 2016-07-20 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol
@@ -150,7 +150,6 @@ files:
150
150
  - spec/higher_level_api/integration/basic_return_spec.rb
151
151
  - spec/higher_level_api/integration/channel_close_spec.rb
152
152
  - spec/higher_level_api/integration/channel_open_spec.rb
153
- - spec/higher_level_api/integration/confirm_select_spec.rb
154
153
  - spec/higher_level_api/integration/connection_recovery_spec.rb
155
154
  - spec/higher_level_api/integration/connection_spec.rb
156
155
  - spec/higher_level_api/integration/connection_stop_spec.rb
@@ -209,6 +208,7 @@ files:
209
208
  - spec/unit/concurrent/condition_spec.rb
210
209
  - spec/unit/concurrent/linked_continuation_queue_spec.rb
211
210
  - spec/unit/concurrent/synchronized_sorted_set_spec.rb
211
+ - spec/unit/exchange_recovery_spec.rb
212
212
  - spec/unit/version_delivery_tag_spec.rb
213
213
  homepage: http://rubybunny.info
214
214
  licenses:
@@ -230,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
230
  version: '0'
231
231
  requirements: []
232
232
  rubyforge_project:
233
- rubygems_version: 2.4.8
233
+ rubygems_version: 2.5.1
234
234
  signing_key:
235
235
  specification_version: 4
236
236
  summary: Popular easy to use Ruby client for RabbitMQ
@@ -250,7 +250,6 @@ test_files:
250
250
  - spec/higher_level_api/integration/basic_return_spec.rb
251
251
  - spec/higher_level_api/integration/channel_close_spec.rb
252
252
  - spec/higher_level_api/integration/channel_open_spec.rb
253
- - spec/higher_level_api/integration/confirm_select_spec.rb
254
253
  - spec/higher_level_api/integration/connection_recovery_spec.rb
255
254
  - spec/higher_level_api/integration/connection_spec.rb
256
255
  - spec/higher_level_api/integration/connection_stop_spec.rb
@@ -309,5 +308,6 @@ test_files:
309
308
  - spec/unit/concurrent/condition_spec.rb
310
309
  - spec/unit/concurrent/linked_continuation_queue_spec.rb
311
310
  - spec/unit/concurrent/synchronized_sorted_set_spec.rb
311
+ - spec/unit/exchange_recovery_spec.rb
312
312
  - spec/unit/version_delivery_tag_spec.rb
313
313
  has_rdoc: true
@@ -1,19 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Bunny::Channel, "#confirm_select" do
4
- let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
6
- c.start
7
- c
8
- end
9
-
10
- after :each do
11
- connection.close if connection.open?
12
- end
13
-
14
- it "is supported" do
15
- connection.with_channel do |ch|
16
- ch.confirm_select
17
- end
18
- end
19
- end