bunny 1.3.0 → 2.17.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.
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