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 +4 -4
- data/CONTRIBUTING.md +1 -1
- data/ChangeLog.md +6 -1
- data/lib/bunny/channel.rb +2 -0
- data/lib/bunny/exchange.rb +11 -2
- data/lib/bunny/queue.rb +1 -1
- data/lib/bunny/session.rb +19 -20
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/connection_recovery_spec.rb +352 -334
- data/spec/higher_level_api/integration/publisher_confirms_spec.rb +12 -0
- data/spec/unit/exchange_recovery_spec.rb +39 -0
- metadata +5 -5
- data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f628f0ace95911e5d5ce48817389f79a3a68415
|
4
|
+
data.tar.gz: a4f867d1fbe68457f311a0b3691a644c882cc1e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 792e2d8e367bca866fa48af83d640b75feb401da8a10711852df09d436ca614cdf0a7c52a72fe2a890cb627991639b771cd0ed6fa3b912bc235206c00da219ec
|
7
|
+
data.tar.gz: 8a3fa96ab3e6103513f1df439c37f8d4ce6688c9dba2c40e7fadbec265bf1cf5e2abd438b16f7e954ff6c1479130ecc0253023c8e6d50248a199ce2a13ab8098
|
data/CONTRIBUTING.md
CHANGED
@@ -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/
|
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`
|
data/ChangeLog.md
CHANGED
data/lib/bunny/channel.rb
CHANGED
data/lib/bunny/exchange.rb
CHANGED
@@ -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
|
-
|
218
|
-
|
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
|
|
data/lib/bunny/queue.rb
CHANGED
data/lib/bunny/session.rb
CHANGED
@@ -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
|
-
|
672
|
-
|
673
|
-
@logger.debug "About to start connection recovery..."
|
672
|
+
sleep @network_recovery_interval
|
673
|
+
@logger.debug "About to start connection recovery..."
|
674
674
|
|
675
|
-
|
675
|
+
self.initialize_transport
|
676
676
|
|
677
|
-
|
678
|
-
|
677
|
+
@logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
|
678
|
+
self.start
|
679
679
|
|
680
|
-
|
681
|
-
|
680
|
+
if open?
|
681
|
+
@recovering_from_network_failure = false
|
682
682
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
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
|
|
data/lib/bunny/version.rb
CHANGED
@@ -1,403 +1,421 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "rabbitmq/http/client"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
20
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
ch.
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
ch.
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
94
|
+
it "recovers publisher confirms setting" do
|
95
|
+
with_open do |c|
|
96
|
+
ch = c.create_channel
|
87
97
|
ch.confirm_select
|
88
|
-
|
89
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
109
|
-
|
110
|
-
end
|
111
|
-
end
|
136
|
+
n = rand
|
137
|
+
s = "bunny.tests.recovery.client-named#{n}"
|
112
138
|
|
113
|
-
|
114
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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
|
-
|
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
|
-
|
272
|
-
|
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
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
-
|
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
|
-
|
304
|
-
|
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
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
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
|
-
|
370
|
-
|
347
|
+
def close_ignoring_permitted_exceptions(connection_name)
|
348
|
+
http_client.close_connection(connection_name)
|
349
|
+
rescue Bunny::ConnectionForced
|
350
|
+
end
|
371
351
|
|
372
|
-
|
352
|
+
def wait_on_loss_and_recovery_of(&probe)
|
353
|
+
poll_while &probe
|
354
|
+
poll_until &probe
|
355
|
+
end
|
373
356
|
|
374
|
-
|
375
|
-
|
357
|
+
def poll_while(&probe)
|
358
|
+
Timeout::timeout(10) {
|
359
|
+
sleep 0.1 while probe[]
|
360
|
+
}
|
361
|
+
end
|
376
362
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
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
|
-
|
385
|
-
|
386
|
-
|
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
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
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
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
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
|
-
|
400
|
-
|
401
|
-
|
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
|
+
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-
|
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.
|
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
|