bunny 2.4.0 → 2.5.0

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