bunny 1.3.0 → 2.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +7 -1
  4. data/.rspec +1 -3
  5. data/.travis.yml +21 -14
  6. data/CONTRIBUTING.md +132 -0
  7. data/ChangeLog.md +887 -1
  8. data/Gemfile +13 -13
  9. data/LICENSE +1 -1
  10. data/README.md +46 -60
  11. data/Rakefile +54 -0
  12. data/bunny.gemspec +5 -11
  13. data/docker-compose.yml +28 -0
  14. data/docker/Dockerfile +24 -0
  15. data/docker/apt/preferences.d/erlang +3 -0
  16. data/docker/apt/sources.list.d/bintray.rabbitmq.list +2 -0
  17. data/docker/docker-entrypoint.sh +26 -0
  18. data/docker/rabbitmq.conf +29 -0
  19. data/examples/connection/automatic_recovery_with_basic_get.rb +1 -1
  20. data/examples/connection/automatic_recovery_with_client_named_queues.rb +1 -1
  21. data/examples/connection/automatic_recovery_with_multiple_consumers.rb +1 -1
  22. data/examples/connection/automatic_recovery_with_republishing.rb +1 -1
  23. data/examples/connection/automatic_recovery_with_server_named_queues.rb +1 -1
  24. data/examples/connection/channel_level_exception.rb +1 -9
  25. data/examples/connection/disabled_automatic_recovery.rb +1 -1
  26. data/examples/connection/heartbeat.rb +1 -1
  27. data/examples/consumers/high_and_low_priority.rb +1 -1
  28. data/examples/guides/extensions/alternate_exchange.rb +2 -0
  29. data/examples/guides/extensions/basic_nack.rb +1 -1
  30. data/examples/guides/extensions/dead_letter_exchange.rb +1 -1
  31. data/examples/guides/getting_started/hello_world.rb +2 -0
  32. data/examples/guides/getting_started/weathr.rb +2 -0
  33. data/examples/guides/queues/one_off_consumer.rb +2 -0
  34. data/examples/guides/queues/redeliveries.rb +4 -2
  35. data/lib/bunny.rb +8 -4
  36. data/lib/bunny/channel.rb +268 -153
  37. data/lib/bunny/channel_id_allocator.rb +6 -4
  38. data/lib/bunny/concurrent/continuation_queue.rb +34 -13
  39. data/lib/bunny/consumer_work_pool.rb +34 -6
  40. data/lib/bunny/cruby/socket.rb +48 -21
  41. data/lib/bunny/cruby/ssl_socket.rb +65 -4
  42. data/lib/bunny/exceptions.rb +25 -4
  43. data/lib/bunny/exchange.rb +24 -28
  44. data/lib/bunny/get_response.rb +1 -1
  45. data/lib/bunny/heartbeat_sender.rb +3 -2
  46. data/lib/bunny/jruby/socket.rb +23 -6
  47. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  48. data/lib/bunny/queue.rb +31 -22
  49. data/lib/bunny/reader_loop.rb +31 -18
  50. data/lib/bunny/session.rb +448 -159
  51. data/lib/bunny/test_kit.rb +14 -0
  52. data/lib/bunny/timeout.rb +1 -12
  53. data/lib/bunny/transport.rb +205 -98
  54. data/lib/bunny/version.rb +1 -1
  55. data/repl +1 -1
  56. data/spec/config/enabled_plugins +1 -0
  57. data/spec/config/rabbitmq.conf +13 -0
  58. data/spec/higher_level_api/integration/basic_ack_spec.rb +175 -16
  59. data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
  60. data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
  61. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
  62. data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
  63. data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
  64. data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
  65. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
  66. data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
  67. data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
  68. data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
  69. data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
  70. data/spec/higher_level_api/integration/connection_recovery_spec.rb +424 -221
  71. data/spec/higher_level_api/integration/connection_spec.rb +300 -126
  72. data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
  73. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
  74. data/spec/higher_level_api/integration/dead_lettering_spec.rb +34 -11
  75. data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
  76. data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
  77. data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
  78. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
  79. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
  80. data/spec/higher_level_api/integration/heartbeat_spec.rb +26 -8
  81. data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
  82. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
  83. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +156 -42
  84. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
  85. data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
  86. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
  87. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  88. data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
  89. data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
  90. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
  91. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
  92. data/spec/higher_level_api/integration/tls_connection_spec.rb +224 -89
  93. data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
  94. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  95. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  96. data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
  97. data/spec/issues/issue100_spec.rb +11 -11
  98. data/spec/issues/issue141_spec.rb +13 -14
  99. data/spec/issues/issue202_spec.rb +1 -1
  100. data/spec/issues/issue224_spec.rb +40 -0
  101. data/spec/issues/issue465_spec.rb +32 -0
  102. data/spec/issues/issue549_spec.rb +30 -0
  103. data/spec/issues/issue78_spec.rb +21 -24
  104. data/spec/issues/issue83_spec.rb +5 -6
  105. data/spec/issues/issue97_spec.rb +44 -45
  106. data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
  107. data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
  108. data/spec/spec_helper.rb +8 -26
  109. data/spec/stress/channel_close_stress_spec.rb +64 -0
  110. data/spec/stress/channel_open_stress_spec.rb +15 -9
  111. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
  112. data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
  113. data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
  114. data/spec/stress/connection_open_close_spec.rb +9 -9
  115. data/spec/stress/merry_go_round_spec.rb +105 -0
  116. data/spec/tls/client_key.pem +49 -25
  117. data/spec/tls/generate-server-cert.sh +8 -0
  118. data/spec/tls/server-openssl.cnf +10 -0
  119. data/spec/tls/server.csr +16 -0
  120. data/spec/tls/server_key.pem +49 -25
  121. data/spec/toxiproxy_helper.rb +28 -0
  122. data/spec/unit/bunny_spec.rb +5 -5
  123. data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
  124. data/spec/unit/concurrent/condition_spec.rb +8 -8
  125. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
  126. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
  127. data/spec/unit/exchange_recovery_spec.rb +39 -0
  128. data/spec/unit/version_delivery_tag_spec.rb +3 -3
  129. metadata +65 -47
  130. data/.ruby-version +0 -1
  131. data/lib/bunny/compatibility.rb +0 -24
  132. data/lib/bunny/system_timer.rb +0 -20
  133. data/spec/compatibility/queue_declare_spec.rb +0 -44
  134. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
  135. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  136. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  137. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  138. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  139. data/spec/stress/long_running_consumer_spec.rb +0 -83
  140. data/spec/tls/cacert.pem +0 -18
  141. data/spec/tls/client_cert.pem +0 -18
  142. data/spec/tls/server_cert.pem +0 -18
  143. data/spec/unit/system_timer_spec.rb +0 -10
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Bunny::Exchange, "#publish" do
4
4
  let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
5
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed")
6
6
  c.start
7
7
  c
8
8
  end
@@ -12,7 +12,7 @@ describe Bunny::Exchange, "#publish" do
12
12
  end
13
13
 
14
14
 
15
- context "with :mandatory => true and a bad [no routes] routing key" do
15
+ context "with mandatory: true and a bad [no routes] routing key" do
16
16
  it "causes a message to be returned" do
17
17
  ch = connection.create_channel
18
18
  x = ch.default_exchange
@@ -22,10 +22,10 @@ describe Bunny::Exchange, "#publish" do
22
22
  returned << content
23
23
  end
24
24
 
25
- x.publish("xyzzy", :routing_key => rand.to_s, :mandatory => true)
25
+ x.publish("xyzzy", routing_key: rand.to_s, mandatory: true)
26
26
  sleep 0.5
27
27
 
28
- returned.should include("xyzzy")
28
+ expect(returned).to include("xyzzy")
29
29
 
30
30
  ch.close
31
31
  end
@@ -1,8 +1,8 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Bunny::Channel, "when closed" do
3
+ describe Bunny::Channel do
4
4
  let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
5
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed")
6
6
  c.start
7
7
  c
8
8
  end
@@ -11,15 +11,56 @@ describe Bunny::Channel, "when closed" do
11
11
  connection.close
12
12
  end
13
13
 
14
- it "releases the id" do
15
- ch = connection.create_channel
16
- n = ch.number
14
+ context "when closed" do
15
+ it "releases the id" do
16
+ ch = connection.create_channel
17
+ n = ch.number
17
18
 
18
- ch.should be_open
19
- ch.close
20
- ch.should be_closed
19
+ expect(ch).to be_open
20
+ ch.close
21
+ expect(ch).to be_closed
21
22
 
22
- # a new channel with the same id can be created
23
- connection.create_channel(n)
23
+ # a new channel with the same id can be created
24
+ connection.create_channel(n)
25
+ end
26
+ end
27
+
28
+ context "when double closed" do
29
+ # bunny#528
30
+ it "raises a meaningful exception" do
31
+ ch = connection.create_channel
32
+
33
+ expect(ch).to be_open
34
+ ch.close
35
+ expect(ch).to be_closed
36
+
37
+ expect { ch.close }.to raise_error(Bunny::ChannelAlreadyClosed)
38
+ end
39
+ end
40
+
41
+ context "when double closed after a channel-level protocol exception" do
42
+ # bunny#528
43
+ it "raises a meaningful exception" do
44
+ ch = connection.create_channel
45
+
46
+ s = "bunny-temp-q-#{rand}"
47
+
48
+ expect(ch).to be_open
49
+ ch.queue_declare(s, durable: false)
50
+
51
+ expect do
52
+ ch.queue_declare(s, durable: true)
53
+ end.to raise_error(Bunny::PreconditionFailed)
54
+
55
+ # channel.close is sent and handled concurrently with the test
56
+ sleep 1
57
+ expect(ch).to be_closed
58
+
59
+ expect { ch.close }.to raise_error(Bunny::ChannelAlreadyClosed)
60
+
61
+ cleanup_ch = connection.create_channel
62
+ cleanup_ch.queue_delete(s)
63
+ cleanup_ch.close
64
+ end
24
65
  end
25
66
  end
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Bunny::Channel, "when opened" do
4
4
  let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed")
5
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed")
6
6
  c.start
7
7
  c
8
8
  end
@@ -13,17 +13,17 @@ describe Bunny::Channel, "when opened" do
13
13
 
14
14
  context "without explicitly provided id" do
15
15
  it "gets an allocated id and is successfully opened" do
16
- connection.should be_connected
16
+ expect(connection).to be_connected
17
17
  ch = connection.create_channel
18
- ch.should be_open
18
+ expect(ch).to be_open
19
19
 
20
- ch.id.should be > 0
20
+ expect(ch.id).to be > 0
21
21
  end
22
22
  end
23
23
 
24
24
  context "with an explicitly provided id = 0" do
25
25
  it "raises ArgumentError" do
26
- connection.should be_connected
26
+ expect(connection).to be_connected
27
27
  expect {
28
28
  connection.create_channel(0)
29
29
  }.to raise_error(ArgumentError)
@@ -34,10 +34,10 @@ describe Bunny::Channel, "when opened" do
34
34
  context "with explicitly provided id" do
35
35
  it "uses that id and is successfully opened" do
36
36
  ch = connection.create_channel(767)
37
- connection.should be_connected
38
- ch.should be_open
37
+ expect(connection).to be_connected
38
+ expect(ch).to be_open
39
39
 
40
- ch.id.should == 767
40
+ expect(ch.id).to eq 767
41
41
  end
42
42
  end
43
43
 
@@ -46,12 +46,12 @@ describe Bunny::Channel, "when opened" do
46
46
  context "with explicitly provided id that is already taken" do
47
47
  it "reuses the channel that is already opened" do
48
48
  ch = connection.create_channel(767)
49
- connection.should be_connected
50
- ch.should be_open
49
+ expect(connection).to be_connected
50
+ expect(ch).to be_open
51
51
 
52
- ch.id.should == 767
52
+ expect(ch.id).to eq 767
53
53
 
54
- connection.create_channel(767).should == ch
54
+ expect(connection.create_channel(767)).to eq ch
55
55
  end
56
56
  end
57
57
  end
@@ -1,280 +1,483 @@
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
- http_client.close_connection(conn_info.name)
12
- end
4
+ require "bunny/concurrent/condition"
5
+
6
+ describe "Connection recovery" do
7
+ let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") }
8
+ let(:logger) { Logger.new($stderr).tap {|logger| logger.level = ENV["BUNNY_LOG_LEVEL"] || Logger::WARN } }
9
+ let(:recovery_interval) { 0.2 }
10
+
11
+ it "reconnects after grace period" do
12
+ with_open do |c|
13
+ close_all_connections!
14
+ wait_for_recovery_with { connections.any? }
15
+ end
16
+ end
17
+
18
+ it "reconnects after grace period (with multiple hosts)" do
19
+ with_open_multi_host do |c|
20
+ close_all_connections!
21
+ wait_for_recovery_with { connections.any? }
22
+ end
23
+ end
24
+
25
+ it "reconnects after grace period (with multiple hosts, including a broken one)" do
26
+ with_open_multi_broken_host do |c|
27
+ close_all_connections!
28
+ wait_for_recovery_with { connections.any? }
13
29
  end
30
+ end
14
31
 
15
- def wait_for_recovery
32
+ it "recovers channels" do
33
+ with_open do |c|
34
+ ch1 = c.create_channel
35
+ ch2 = c.create_channel
16
36
  sleep 1.5
37
+ close_all_connections!
38
+ sleep 0.5
39
+ poll_until { channels.count == 2 }
40
+ expect(ch1).to be_open
41
+ expect(ch2).to be_open
17
42
  end
43
+ end
18
44
 
19
- def with_open(c = Bunny.new(:network_recovery_interval => 0.2, :recover_from_connection_close => true), &block)
20
- begin
21
- c.start
22
- block.call(c)
23
- ensure
24
- c.close
45
+ it "provides a recovery completion callback" do
46
+ with_open do |c|
47
+ latch = Bunny::Concurrent::Condition.new
48
+ c.after_recovery_completed do
49
+ latch.notify
25
50
  end
51
+
52
+ ch = c.create_channel
53
+ sleep 1.0
54
+ close_all_connections!
55
+ poll_until { c.open? && ch.open? }
56
+ poll_until { latch.none_threads_waiting? }
57
+ end
58
+ end
59
+
60
+ it "recovers channels (with multiple hosts)" do
61
+ with_open_multi_host do |c|
62
+ ch1 = c.create_channel
63
+ ch2 = c.create_channel
64
+ sleep 1.5
65
+ close_all_connections!
66
+ sleep 0.5
67
+ poll_until { channels.count == 2 }
68
+ expect(ch1).to be_open
69
+ expect(ch2).to be_open
70
+ end
71
+ end
72
+
73
+ it "recovers channels (with multiple hosts, including a broken one)" do
74
+ with_open_multi_broken_host do |c|
75
+ ch1 = c.create_channel
76
+ ch2 = c.create_channel
77
+ sleep 1.5
78
+ close_all_connections!
79
+ sleep 0.5
80
+ poll_until { channels.count == 2 }
81
+ expect(ch1).to be_open
82
+ expect(ch2).to be_open
83
+ end
84
+ end
85
+
86
+ it "recovers basic.qos prefetch setting" do
87
+ with_open do |c|
88
+ ch = c.create_channel
89
+ ch.prefetch(11)
90
+ expect(ch.prefetch_count).to eq 11
91
+ expect(ch.prefetch_global).to be false
92
+ sleep 1.5
93
+ close_all_connections!
94
+ sleep 0.5
95
+ wait_for_recovery_with { connections.any? }
96
+ expect(ch).to be_open
97
+ expect(ch.prefetch_count).to eq 11
98
+ expect(ch.prefetch_global).to be false
99
+ end
100
+ end
101
+
102
+ it "recovers basic.qos prefetch global setting" do
103
+ with_open do |c|
104
+ ch = c.create_channel
105
+ ch.prefetch(42, true)
106
+ expect(ch.prefetch_count).to eq 42
107
+ expect(ch.prefetch_global).to be true
108
+ sleep 1.5
109
+ close_all_connections!
110
+ sleep 0.5
111
+ wait_for_recovery_with { connections.any? }
112
+ expect(ch).to be_open
113
+ expect(ch.prefetch_count).to eq 42
114
+ expect(ch.prefetch_global).to be true
26
115
  end
116
+ end
27
117
 
28
- def ensure_queue_recovery(ch, q)
29
- q.purge
30
- x = ch.default_exchange
31
- x.publish("msg", :routing_key => q.name)
118
+ it "recovers publisher confirms setting" do
119
+ with_open do |c|
120
+ ch = c.create_channel
121
+ ch.confirm_select
122
+ expect(ch).to be_using_publisher_confirms
123
+ sleep 1.5
124
+ close_all_connections!
32
125
  sleep 0.5
33
- q.message_count.should == 1
34
- q.purge
126
+ wait_for_recovery_with { connections.any? }
127
+ expect(ch).to be_open
128
+ expect(ch).to be_using_publisher_confirms
35
129
  end
130
+ end
36
131
 
37
- def ensure_queue_binding_recovery(x, q, routing_key = "")
38
- q.purge
39
- x.publish("msg", :routing_key => routing_key)
132
+ it "recovers transactionality setting" do
133
+ with_open do |c|
134
+ ch = c.create_channel
135
+ ch.tx_select
136
+ expect(ch).to be_using_tx
137
+ sleep 1.5
138
+ close_all_connections!
40
139
  sleep 0.5
41
- q.message_count.should == 1
42
- q.purge
140
+ wait_for_recovery_with { connections.any? }
141
+ expect(ch).to be_open
142
+ expect(ch).to be_using_tx
143
+ end
144
+ end
145
+
146
+ it "recovers client-named queues" do
147
+ with_open do |c|
148
+ ch = c.create_channel
149
+ q = ch.queue("bunny.tests.recovery.client-named#{rand}")
150
+ close_all_connections!
151
+ wait_for_recovery_with { connections.any? }
152
+ expect(ch).to be_open
153
+ ensure_queue_recovery(ch, q)
154
+ q.delete
43
155
  end
156
+ end
157
+
158
+ # a very simplistic test for queues inspired by #422
159
+ it "recovers client-named queues declared with no_declare: true" do
160
+ with_open do |c|
161
+ ch = c.create_channel
162
+ ch2 = c.create_channel
44
163
 
45
- def ensure_exchange_binding_recovery(ch, source, destination, routing_key = "")
46
- q = ch.queue("", :exclusive => true)
47
- q.bind(destination, :routing_key => routing_key)
164
+ n = rand
165
+ s = "bunny.tests.recovery.client-named#{n}"
48
166
 
49
- source.publish("msg", :routing_key => routing_key)
50
- q.message_count.should == 1
167
+ q = ch.queue(s)
168
+ q2 = ch2.queue(s, no_declare: true)
169
+
170
+ close_all_connections!
171
+ wait_for_recovery_with { connections.any? }
172
+ expect(ch).to be_open
173
+ ensure_queue_recovery(ch, q)
51
174
  q.delete
52
175
  end
176
+ end
53
177
 
54
- #
55
- # Examples
56
- #
178
+ # a test for #422
179
+ it "recovers client-named queues declared with passive: true" do
180
+ with_open do |c|
181
+ ch = c.create_channel
182
+ ch2 = c.create_channel
57
183
 
58
- it "reconnects after grace period" do
59
- with_open do |c|
60
- close_all_connections!
61
- sleep 0.1
62
- c.should_not be_open
184
+ n = rand
185
+ s = "bunny.tests.recovery.client-named#{n}"
63
186
 
64
- wait_for_recovery
65
- c.should be_open
66
- end
187
+ q = ch.queue(s)
188
+ q2 = ch2.queue(s, passive: true)
189
+
190
+ close_all_connections!
191
+ wait_for_recovery_with { connections.any? }
192
+ expect(ch).to be_open
193
+ ensure_queue_recovery(ch, q)
194
+ ensure_queue_recovery(ch, q2)
195
+ q.delete
67
196
  end
197
+ end
68
198
 
69
- it "recovers channel" do
70
- with_open do |c|
71
- ch1 = c.create_channel
72
- ch2 = c.create_channel
73
- close_all_connections!
74
- sleep 0.1
75
- c.should_not be_open
76
-
77
- wait_for_recovery
78
- ch1.should be_open
79
- ch2.should be_open
80
- end
199
+ it "recovers server-named queues" do
200
+ with_open do |c|
201
+ ch = c.create_channel
202
+ q = ch.queue("", exclusive: true)
203
+ close_all_connections!
204
+ wait_for_recovery_with { connections.any? }
205
+ expect(ch).to be_open
206
+ ensure_queue_recovery(ch, q)
81
207
  end
208
+ end
82
209
 
83
- it "recovers basic.qos prefetch setting" do
84
- with_open do |c|
85
- ch = c.create_channel
86
- ch.prefetch(11)
87
- ch.prefetch_count.should == 11
88
- close_all_connections!
89
- sleep 0.1
90
- c.should_not be_open
91
-
92
- wait_for_recovery
93
- ch.should be_open
94
- ch.prefetch_count.should == 11
95
- end
210
+ it "recovers queue bindings" do
211
+ with_open do |c|
212
+ ch = c.create_channel
213
+ x = ch.fanout("amq.fanout")
214
+ q = ch.queue("", exclusive: true)
215
+ q.bind(x)
216
+ close_all_connections!
217
+ wait_for_recovery_with { connections.any? }
218
+ expect(ch).to be_open
219
+ ensure_queue_binding_recovery(ch, x, q)
96
220
  end
221
+ end
97
222
 
223
+ it "recovers exchanges and their bindings" do
224
+ with_open do |c|
225
+ ch = c.create_channel
226
+ source = ch.fanout("source.exchange.recovery.example", auto_delete: true)
227
+ destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true)
98
228
 
99
- it "recovers publisher confirms setting" do
100
- with_open do |c|
101
- ch = c.create_channel
102
- ch.confirm_select
103
- ch.should be_using_publisher_confirms
104
- close_all_connections!
105
- sleep 0.1
106
- c.should_not be_open
229
+ destination.bind(source)
107
230
 
108
- wait_for_recovery
109
- ch.should be_open
110
- ch.should be_using_publisher_confirms
111
- end
112
- end
231
+ # Exchanges won't get auto-deleted on connection loss unless they have
232
+ # had an exclusive queue bound to them.
233
+ dst_queue = ch.queue("", exclusive: true)
234
+ dst_queue.bind(destination, routing_key: "")
113
235
 
114
- it "recovers transactionality setting" do
115
- with_open do |c|
116
- ch = c.create_channel
117
- ch.tx_select
118
- ch.should be_using_tx
119
- close_all_connections!
120
- sleep 0.1
121
- c.should_not be_open
122
-
123
- wait_for_recovery
124
- ch.should be_open
125
- ch.should be_using_tx
126
- end
236
+ src_queue = ch.queue("", exclusive: true)
237
+ src_queue.bind(source, routing_key: "")
238
+
239
+ close_all_connections!
240
+
241
+ wait_for_recovery_with { connections.any? && exchange_names_in_vhost("/").include?(source.name) }
242
+ ch.confirm_select
243
+
244
+ source.publish("msg", routing_key: "")
245
+ ch.wait_for_confirms
246
+ expect(dst_queue.message_count).to eq 1
247
+ destination.delete
127
248
  end
249
+ end
128
250
 
129
- it "recovers client-named queues" do
130
- with_open do |c|
131
- ch = c.create_channel
132
- q = ch.queue("bunny.tests.recovery.client-named#{rand}")
133
- close_all_connections!
134
- sleep 0.1
135
- c.should_not be_open
136
-
137
- wait_for_recovery
138
- ch.should be_open
139
- ensure_queue_recovery(ch, q)
140
- q.delete
141
- end
251
+ it "recovers passively declared exchanges and their bindings" do
252
+ with_open do |c|
253
+ ch = c.create_channel
254
+ ch.confirm_select
255
+
256
+ source = ch.fanout("amq.fanout", passive: true)
257
+ destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true)
258
+
259
+ destination.bind(source)
260
+
261
+ # Exchanges won't get auto-deleted on connection loss unless they have
262
+ # had an exclusive queue bound to them.
263
+ dst_queue = ch.queue("", exclusive: true)
264
+ dst_queue.bind(destination, routing_key: "")
265
+
266
+ src_queue = ch.queue("", exclusive: true)
267
+ src_queue.bind(source, routing_key: "")
268
+
269
+ close_all_connections!
270
+
271
+ wait_for_recovery_with { connections.any? }
272
+
273
+ source.publish("msg", routing_key: "")
274
+ ch.wait_for_confirms
275
+
276
+ expect(dst_queue.message_count).to eq 1
277
+ destination.delete
142
278
  end
279
+ end
143
280
 
281
+ # this is a simplistic test that primarily execises the code path from #412
282
+ it "recovers exchanges that were declared with passive = true" do
283
+ with_open do |c|
284
+ ch = c.create_channel
285
+ ch2 = c.create_channel
286
+ source = ch.fanout("source.exchange.recovery.example", auto_delete: true)
287
+ destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true)
144
288
 
145
- it "recovers server-named queues" do
146
- with_open do |c|
147
- ch = c.create_channel
148
- q = ch.queue("", :exclusive => true)
149
- close_all_connections!
150
- sleep 0.1
151
- c.should_not be_open
289
+ source2 = ch2.fanout("source.exchange.recovery.example", no_declare: true)
152
290
 
153
- wait_for_recovery
154
- ch.should be_open
155
- ensure_queue_recovery(ch, q)
156
- end
291
+ destination.bind(source)
292
+
293
+ # Exchanges won't get auto-deleted on connection loss unless they have
294
+ # had an exclusive queue bound to them.
295
+ dst_queue = ch.queue("", exclusive: true)
296
+ dst_queue.bind(destination, routing_key: "")
297
+
298
+ src_queue = ch.queue("", exclusive: true)
299
+ src_queue.bind(source, routing_key: "")
300
+
301
+ close_all_connections!
302
+
303
+ wait_for_recovery_with { connections.any? && exchange_names_in_vhost("/").include?(source.name) }
304
+
305
+ ch2.confirm_select
306
+
307
+ source2.publish("msg", routing_key: "")
308
+ ch2.wait_for_confirms
309
+ expect(dst_queue.message_count).to eq 1
157
310
  end
311
+ end
158
312
 
159
- it "recovers queue bindings" do
160
- with_open do |c|
161
- ch = c.create_channel
162
- x = ch.fanout("amq.fanout")
163
- q = ch.queue("", :exclusive => true)
164
- q.bind(x)
165
- close_all_connections!
166
- sleep 0.1
167
- c.should_not be_open
168
-
169
- wait_for_recovery
170
- ch.should be_open
171
- ensure_queue_binding_recovery(x, q)
172
- end
313
+ it "recovers allocated channel ids" do
314
+ with_open do |c|
315
+ q = "queue#{Time.now.to_i}"
316
+ 10.times { c.create_channel }
317
+ expect(c.queue_exists?(q)).to eq false
318
+ close_all_connections!
319
+ wait_for_recovery_with { channels.any? }
320
+ # make sure the connection isn't closed shortly after
321
+ # due to "second 'channel.open' seen". MK.
322
+ expect(c).to be_open
323
+ sleep 0.1
324
+ expect(c).to be_open
325
+ sleep 0.1
326
+ expect(c).to be_open
173
327
  end
328
+ end
174
329
 
175
- it "recovers exchange bindings" do
176
- with_open do |c|
177
- ch = c.create_channel
178
- x = ch.fanout("amq.fanout")
179
- x2 = ch.fanout("bunny.tests.recovery.fanout")
180
- x2.bind(x)
181
- close_all_connections!
182
- sleep 0.1
183
- c.should_not be_open
184
-
185
- wait_for_recovery
186
- ch.should be_open
187
- ensure_exchange_binding_recovery(ch, x, x2)
330
+ it "recovers consumers" do
331
+ with_open do |c|
332
+ delivered = false
333
+
334
+ ch = c.create_channel
335
+ ch.confirm_select
336
+ q = ch.queue("", exclusive: true)
337
+ q.subscribe do |_, _, _|
338
+ delivered = true
188
339
  end
340
+ close_all_connections!
341
+ wait_for_recovery_with { connections.any? }
342
+ expect(ch).to be_open
343
+
344
+ q.publish("")
345
+ ch.wait_for_confirms
346
+
347
+ poll_until { delivered }
189
348
  end
349
+ end
190
350
 
191
- it "recovers allocated channel ids" do
192
- with_open do |c|
193
- q = "queue#{Time.now.to_i}"
194
- 10.times { c.create_channel }
195
- c.queue_exists?(q).should be_false
196
- close_all_connections!
197
- sleep 0.1
198
- c.should_not be_open
199
-
200
- wait_for_recovery
201
- c.queue_exists?(q).should be_false
202
- # make sure the connection isn't closed shortly after
203
- # due to "second 'channel.open' seen". MK.
204
- c.should be_open
205
- sleep 0.1
206
- c.should be_open
207
- sleep 0.1
208
- c.should be_open
209
- end
351
+ it "recovers all consumers" do
352
+ n = 32
353
+
354
+ with_open do |c|
355
+ ch = c.create_channel
356
+ q = ch.queue("", exclusive: true)
357
+ n.times { q.subscribe { |_, _, _| } }
358
+ close_all_connections!
359
+ wait_for_recovery_with { connections.any? }
360
+ expect(ch).to be_open
361
+ sleep 0.5
362
+
363
+ expect(q.consumer_count).to eq n
210
364
  end
365
+ end
366
+
367
+ it "recovers all queues" do
368
+ n = 32
369
+
370
+ qs = []
211
371
 
212
- it "recovers consumers" do
213
- with_open do |c|
214
- delivered = false
215
-
216
- ch = c.create_channel
217
- q = ch.queue("", :exclusive => true)
218
- q.subscribe do |_, _, _|
219
- delivered = true
220
- end
221
- close_all_connections!
222
- sleep 0.1
223
- c.should_not be_open
224
-
225
- wait_for_recovery
226
- ch.should be_open
227
-
228
- q.publish("")
229
- sleep 0.5
230
- expect(delivered).to be_true
372
+ with_open do |c|
373
+ ch = c.create_channel
374
+
375
+ n.times do
376
+ qs << ch.queue("", exclusive: true)
231
377
  end
232
- end
378
+ close_all_connections!
379
+ wait_for_recovery_with { queue_names.include?(qs.first.name) }
380
+ sleep 0.5
381
+ expect(ch).to be_open
233
382
 
234
- it "recovers all consumers" do
235
- n = 1024
236
-
237
- with_open do |c|
238
- ch = c.create_channel
239
- q = ch.queue("", :exclusive => true)
240
- n.times do
241
- q.subscribe do |_, _, _|
242
- delivered = true
243
- end
244
- end
245
- close_all_connections!
246
- sleep 0.1
247
- c.should_not be_open
248
-
249
- wait_for_recovery
250
- ch.should be_open
251
-
252
- q.consumer_count.should == n
383
+ qs.each do |q|
384
+ ch.queue_declare(q.name, passive: true)
253
385
  end
254
386
  end
387
+ end
255
388
 
256
- it "recovers all queues" do
257
- n = 256
258
-
259
- qs = []
389
+ def exchange_names_in_vhost(vhost)
390
+ http_client.list_exchanges(vhost).map {|e| e["name"]}
391
+ end
260
392
 
261
- with_open do |c|
262
- ch = c.create_channel
393
+ def connections
394
+ http_client.list_connections
395
+ end
263
396
 
264
- n.times do
265
- qs << ch.queue("", :exclusive => true)
266
- end
267
- close_all_connections!
268
- sleep 0.1
269
- c.should_not be_open
397
+ def channels
398
+ http_client.list_channels
399
+ end
270
400
 
271
- wait_for_recovery
272
- ch.should be_open
401
+ def queue_names
402
+ http_client.list_queues.map {|q| q["name"]}
403
+ end
273
404
 
274
- qs.each do |q|
275
- ch.queue_declare(q.name, :passive => true)
276
- end
277
- end
405
+ def close_all_connections!
406
+ # let whatever actions were taken before
407
+ # this call a chance to propagate, e.g. to make
408
+ # sure that connections are accounted for in the
409
+ # stats DB.
410
+ #
411
+ # See bin/ci/before_build for management plugin
412
+ # pre-configuration.
413
+ #
414
+ # MK.
415
+ sleep 1.1
416
+ connections.each do |conn_info|
417
+ close_ignoring_permitted_exceptions(conn_info.name)
278
418
  end
279
419
  end
420
+
421
+ def close_ignoring_permitted_exceptions(connection_name)
422
+ http_client.close_connection(connection_name)
423
+ rescue Bunny::ConnectionForced, Faraday::ResourceNotFound
424
+ # ignored
425
+ end
426
+
427
+ def wait_for_recovery_with(&probe)
428
+ poll_until &probe
429
+ end
430
+
431
+ def poll_while(&probe)
432
+ Bunny::TestKit.poll_while(&probe)
433
+ end
434
+
435
+ def poll_until(&probe)
436
+ Bunny::TestKit.poll_until(&probe)
437
+ end
438
+
439
+ def with_open(c = Bunny.new(network_recovery_interval: recovery_interval,
440
+ recover_from_connection_close: true,
441
+ logger: logger), &block)
442
+ c.start
443
+ block.call(c)
444
+ ensure
445
+ c.close(false) rescue nil
446
+ end
447
+
448
+ def with_open_multi_host(&block)
449
+ c = Bunny.new(hosts: ["127.0.0.1", "localhost"],
450
+ network_recovery_interval: recovery_interval,
451
+ recover_from_connection_close: true,
452
+ logger: logger)
453
+ with_open(c, &block)
454
+ end
455
+
456
+ def with_open_multi_broken_host(&block)
457
+ c = Bunny.new(hosts: ["broken", "127.0.0.1", "localhost"],
458
+ hosts_shuffle_strategy: Proc.new { |hosts| hosts }, # We do not shuffle for these tests so we always hit the broken host
459
+ network_recovery_interval: recovery_interval,
460
+ recover_from_connection_close: true,
461
+ logger: logger)
462
+ with_open(c, &block)
463
+ end
464
+
465
+ def ensure_queue_recovery(ch, q)
466
+ ch.confirm_select
467
+ q.purge
468
+ x = ch.default_exchange
469
+ x.publish("msg", routing_key: q.name)
470
+ ch.wait_for_confirms
471
+ expect(q.message_count).to eq 1
472
+ q.purge
473
+ end
474
+
475
+ def ensure_queue_binding_recovery(ch, x, q, routing_key = "")
476
+ ch.confirm_select
477
+ q.purge
478
+ x.publish("msg", routing_key: routing_key)
479
+ ch.wait_for_confirms
480
+ expect(q.message_count).to eq 1
481
+ q.purge
482
+ end
280
483
  end