bunny 1.7.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 (141) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +18 -0
  3. data/.gitignore +6 -1
  4. data/.rspec +1 -3
  5. data/.travis.yml +21 -14
  6. data/CONTRIBUTING.md +132 -0
  7. data/ChangeLog.md +745 -1
  8. data/Gemfile +13 -13
  9. data/LICENSE +1 -1
  10. data/README.md +41 -75
  11. data/Rakefile +54 -0
  12. data/bunny.gemspec +4 -10
  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/getting_started/hello_world.rb +2 -0
  30. data/examples/guides/getting_started/weathr.rb +2 -0
  31. data/examples/guides/queues/one_off_consumer.rb +2 -0
  32. data/examples/guides/queues/redeliveries.rb +2 -0
  33. data/lib/bunny.rb +6 -2
  34. data/lib/bunny/channel.rb +192 -109
  35. data/lib/bunny/channel_id_allocator.rb +6 -4
  36. data/lib/bunny/concurrent/continuation_queue.rb +34 -13
  37. data/lib/bunny/consumer_work_pool.rb +34 -6
  38. data/lib/bunny/cruby/socket.rb +29 -16
  39. data/lib/bunny/cruby/ssl_socket.rb +20 -7
  40. data/lib/bunny/exceptions.rb +7 -1
  41. data/lib/bunny/exchange.rb +11 -7
  42. data/lib/bunny/get_response.rb +1 -1
  43. data/lib/bunny/heartbeat_sender.rb +3 -2
  44. data/lib/bunny/jruby/socket.rb +23 -6
  45. data/lib/bunny/jruby/ssl_socket.rb +5 -0
  46. data/lib/bunny/queue.rb +12 -10
  47. data/lib/bunny/reader_loop.rb +31 -18
  48. data/lib/bunny/session.rb +389 -134
  49. data/lib/bunny/test_kit.rb +14 -0
  50. data/lib/bunny/timeout.rb +1 -12
  51. data/lib/bunny/transport.rb +114 -67
  52. data/lib/bunny/version.rb +1 -1
  53. data/repl +1 -1
  54. data/spec/config/rabbitmq.conf +13 -0
  55. data/spec/higher_level_api/integration/basic_ack_spec.rb +154 -22
  56. data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
  57. data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
  58. data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
  59. data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
  60. data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
  61. data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
  62. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
  63. data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
  64. data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
  65. data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
  66. data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
  67. data/spec/higher_level_api/integration/connection_recovery_spec.rb +412 -286
  68. data/spec/higher_level_api/integration/connection_spec.rb +284 -134
  69. data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
  70. data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
  71. data/spec/higher_level_api/integration/dead_lettering_spec.rb +14 -14
  72. data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
  73. data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
  74. data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
  75. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
  76. data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
  77. data/spec/higher_level_api/integration/heartbeat_spec.rb +4 -4
  78. data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
  79. data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
  80. data/spec/higher_level_api/integration/publisher_confirms_spec.rb +92 -27
  81. data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
  82. data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
  83. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
  84. data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
  85. data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
  86. data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
  87. data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
  88. data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
  89. data/spec/higher_level_api/integration/tls_connection_spec.rb +218 -112
  90. data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
  91. data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
  92. data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
  93. data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
  94. data/spec/issues/issue100_spec.rb +11 -12
  95. data/spec/issues/issue141_spec.rb +13 -14
  96. data/spec/issues/issue202_spec.rb +1 -1
  97. data/spec/issues/issue224_spec.rb +5 -5
  98. data/spec/issues/issue465_spec.rb +32 -0
  99. data/spec/issues/issue549_spec.rb +30 -0
  100. data/spec/issues/issue78_spec.rb +21 -24
  101. data/spec/issues/issue83_spec.rb +5 -6
  102. data/spec/issues/issue97_spec.rb +44 -45
  103. data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
  104. data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
  105. data/spec/spec_helper.rb +2 -19
  106. data/spec/stress/channel_close_stress_spec.rb +3 -3
  107. data/spec/stress/channel_open_stress_spec.rb +4 -4
  108. data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
  109. data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
  110. data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
  111. data/spec/stress/connection_open_close_spec.rb +9 -9
  112. data/spec/stress/merry_go_round_spec.rb +105 -0
  113. data/spec/tls/ca_certificate.pem +27 -16
  114. data/spec/tls/ca_key.pem +52 -27
  115. data/spec/tls/client_certificate.pem +27 -16
  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_certificate.pem +27 -16
  121. data/spec/tls/server_key.pem +49 -25
  122. data/spec/toxiproxy_helper.rb +28 -0
  123. data/spec/unit/bunny_spec.rb +5 -5
  124. data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
  125. data/spec/unit/concurrent/condition_spec.rb +8 -8
  126. data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
  127. data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
  128. data/spec/unit/exchange_recovery_spec.rb +39 -0
  129. data/spec/unit/version_delivery_tag_spec.rb +3 -3
  130. metadata +42 -35
  131. data/lib/bunny/system_timer.rb +0 -20
  132. data/spec/config/rabbitmq.config +0 -18
  133. data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
  134. data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
  135. data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
  136. data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
  137. data/spec/stress/long_running_consumer_spec.rb +0 -83
  138. data/spec/tls/cacert.pem +0 -18
  139. data/spec/tls/client_cert.pem +0 -18
  140. data/spec/tls/server_cert.pem +0 -18
  141. 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,357 +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
- begin
12
- http_client.close_connection(conn_info.name)
13
- rescue Bunny::ConnectionForced
14
- # This is not a problem, but the specs intermittently believe it is.
15
- end
16
- end
4
+ 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? }
17
22
  end
23
+ end
18
24
 
19
- def wait_for_recovery
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? }
29
+ end
30
+ end
31
+
32
+ it "recovers channels" do
33
+ with_open do |c|
34
+ ch1 = c.create_channel
35
+ ch2 = c.create_channel
20
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
21
42
  end
43
+ end
22
44
 
23
- def with_open(c = Bunny.new(:network_recovery_interval => 0.2, :recover_from_connection_close => true), &block)
24
- begin
25
- c.start
26
- block.call(c)
27
- ensure
28
- c.close
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
29
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? }
30
57
  end
58
+ end
31
59
 
32
- def with_open_multi_host( c = Bunny.new( :hosts => ["127.0.0.1", "localhost"],
33
- :network_recovery_interval => 0.2,
34
- :recover_from_connection_close => true), &block)
35
- begin
36
- c.start
37
- block.call(c)
38
- ensure
39
- c.close
40
- end
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
41
70
  end
71
+ end
42
72
 
43
- def with_open_multi_broken_host( c = Bunny.new( :hosts => ["broken", "127.0.0.1", "localhost"],
44
- :hosts_shuffle_strategy => Proc.new { |hosts| hosts }, # We do not shuffle for these tests so we always hit the broken host
45
- :network_recovery_interval => 0.2,
46
- :recover_from_connection_close => true), &block)
47
- begin
48
- c.start
49
- block.call(c)
50
- ensure
51
- c.close
52
- end
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
53
83
  end
84
+ end
54
85
 
55
- def ensure_queue_recovery(ch, q)
56
- q.purge
57
- x = ch.default_exchange
58
- x.publish("msg", :routing_key => q.name)
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!
59
94
  sleep 0.5
60
- q.message_count.should == 1
61
- q.purge
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
62
99
  end
100
+ end
63
101
 
64
- def ensure_queue_binding_recovery(x, q, routing_key = "")
65
- q.purge
66
- x.publish("msg", :routing_key => routing_key)
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
115
+ end
116
+ end
117
+
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!
67
125
  sleep 0.5
68
- q.message_count.should == 1
69
- q.purge
126
+ wait_for_recovery_with { connections.any? }
127
+ expect(ch).to be_open
128
+ expect(ch).to be_using_publisher_confirms
70
129
  end
130
+ end
71
131
 
72
- def ensure_exchange_binding_recovery(ch, source, destination, routing_key = "")
73
- q = ch.queue("", :exclusive => true)
74
- q.bind(destination, :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!
139
+ sleep 0.5
140
+ wait_for_recovery_with { connections.any? }
141
+ expect(ch).to be_open
142
+ expect(ch).to be_using_tx
143
+ end
144
+ end
75
145
 
76
- source.publish("msg", :routing_key => routing_key)
77
- q.message_count.should == 1
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)
78
154
  q.delete
79
155
  end
156
+ end
80
157
 
81
- #
82
- # Examples
83
- #
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
84
163
 
85
- it "reconnects after grace period" do
86
- with_open do |c|
87
- close_all_connections!
88
- sleep 0.1
89
- c.should_not be_open
164
+ n = rand
165
+ s = "bunny.tests.recovery.client-named#{n}"
90
166
 
91
- wait_for_recovery
92
- c.should be_open
93
- end
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)
174
+ q.delete
94
175
  end
176
+ end
95
177
 
96
- it "reconnects after grace period (with multiple hosts)" do
97
- with_open_multi_host do |c|
98
- close_all_connections!
99
- sleep 0.1
100
- c.should_not be_open
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
101
183
 
102
- wait_for_recovery
103
- c.should be_open
104
- end
105
- end
184
+ n = rand
185
+ s = "bunny.tests.recovery.client-named#{n}"
106
186
 
107
- it "reconnects after grace period (with multiple hosts, including a broken one)" do
108
- with_open_multi_broken_host do |c|
109
- close_all_connections!
110
- sleep 0.1
111
- c.should_not be_open
187
+ q = ch.queue(s)
188
+ q2 = ch2.queue(s, passive: true)
112
189
 
113
- wait_for_recovery
114
- c.should be_open
115
- end
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
116
196
  end
197
+ end
117
198
 
118
- it "recovers channels" do
119
- with_open do |c|
120
- ch1 = c.create_channel
121
- ch2 = c.create_channel
122
- close_all_connections!
123
- sleep 0.1
124
- c.should_not be_open
125
-
126
- wait_for_recovery
127
- ch1.should be_open
128
- ch2.should be_open
129
- 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)
130
207
  end
208
+ end
131
209
 
132
- it "recovers channels (with multiple hosts)" do
133
- with_open_multi_host do |c|
134
- ch1 = c.create_channel
135
- ch2 = c.create_channel
136
- close_all_connections!
137
- sleep 0.1
138
- c.should_not be_open
139
-
140
- wait_for_recovery
141
- ch1.should be_open
142
- ch2.should be_open
143
- 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)
144
220
  end
221
+ end
145
222
 
146
- it "recovers channels (with multiple hosts, including a broken one)" do
147
- with_open_multi_broken_host do |c|
148
- ch1 = c.create_channel
149
- ch2 = c.create_channel
150
- close_all_connections!
151
- sleep 0.1
152
- c.should_not be_open
153
-
154
- wait_for_recovery
155
- ch1.should be_open
156
- ch2.should be_open
157
- end
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)
228
+
229
+ destination.bind(source)
230
+
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: "")
235
+
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
158
248
  end
249
+ end
159
250
 
160
- it "recovers basic.qos prefetch setting" do
161
- with_open do |c|
162
- ch = c.create_channel
163
- ch.prefetch(11)
164
- ch.prefetch_count.should == 11
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
- ch.prefetch_count.should == 11
172
- 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
173
278
  end
279
+ end
174
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)
175
288
 
176
- it "recovers publisher confirms setting" do
177
- with_open do |c|
178
- ch = c.create_channel
179
- ch.confirm_select
180
- ch.should be_using_publisher_confirms
181
- close_all_connections!
182
- sleep 0.1
183
- c.should_not be_open
289
+ source2 = ch2.fanout("source.exchange.recovery.example", no_declare: true)
184
290
 
185
- wait_for_recovery
186
- ch.should be_open
187
- ch.should be_using_publisher_confirms
188
- 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
189
310
  end
311
+ end
190
312
 
191
- it "recovers transactionality setting" do
192
- with_open do |c|
193
- ch = c.create_channel
194
- ch.tx_select
195
- ch.should be_using_tx
196
- close_all_connections!
197
- sleep 0.1
198
- c.should_not be_open
199
-
200
- wait_for_recovery
201
- ch.should be_open
202
- ch.should be_using_tx
203
- 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
204
327
  end
328
+ end
205
329
 
206
- it "recovers client-named queues" do
207
- with_open do |c|
208
- ch = c.create_channel
209
- q = ch.queue("bunny.tests.recovery.client-named#{rand}")
210
- close_all_connections!
211
- sleep 0.1
212
- c.should_not be_open
213
-
214
- wait_for_recovery
215
- ch.should be_open
216
- ensure_queue_recovery(ch, q)
217
- q.delete
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
218
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 }
219
348
  end
349
+ end
220
350
 
351
+ it "recovers all consumers" do
352
+ n = 32
221
353
 
222
- it "recovers server-named queues" do
223
- with_open do |c|
224
- ch = c.create_channel
225
- q = ch.queue("", :exclusive => true)
226
- close_all_connections!
227
- sleep 0.1
228
- c.should_not be_open
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
229
362
 
230
- wait_for_recovery
231
- ch.should be_open
232
- ensure_queue_recovery(ch, q)
233
- end
363
+ expect(q.consumer_count).to eq n
234
364
  end
365
+ end
235
366
 
236
- it "recovers queue bindings" do
237
- with_open do |c|
238
- ch = c.create_channel
239
- x = ch.fanout("amq.fanout")
240
- q = ch.queue("", :exclusive => true)
241
- q.bind(x)
242
- close_all_connections!
243
- sleep 0.1
244
- c.should_not be_open
245
-
246
- wait_for_recovery
247
- ch.should be_open
248
- ensure_queue_binding_recovery(x, q)
249
- end
250
- end
367
+ it "recovers all queues" do
368
+ n = 32
251
369
 
252
- it "recovers exchange bindings" do
253
- with_open do |c|
254
- ch = c.create_channel
255
- x = ch.fanout("amq.fanout")
256
- x2 = ch.fanout("bunny.tests.recovery.fanout")
257
- x2.bind(x)
258
- close_all_connections!
259
- sleep 0.1
260
- c.should_not be_open
261
-
262
- wait_for_recovery
263
- ch.should be_open
264
- ensure_exchange_binding_recovery(ch, x, x2)
265
- end
266
- end
370
+ qs = []
267
371
 
268
- it "recovers allocated channel ids" do
269
- with_open do |c|
270
- q = "queue#{Time.now.to_i}"
271
- 10.times { c.create_channel }
272
- c.queue_exists?(q).should be_false
273
- close_all_connections!
274
- sleep 0.1
275
- c.should_not be_open
276
-
277
- wait_for_recovery
278
- c.queue_exists?(q).should be_false
279
- # make sure the connection isn't closed shortly after
280
- # due to "second 'channel.open' seen". MK.
281
- c.should be_open
282
- sleep 0.1
283
- c.should be_open
284
- sleep 0.1
285
- c.should be_open
372
+ with_open do |c|
373
+ ch = c.create_channel
374
+
375
+ n.times do
376
+ qs << ch.queue("", exclusive: true)
286
377
  end
287
- 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
288
382
 
289
- it "recovers consumers" do
290
- with_open do |c|
291
- delivered = false
292
-
293
- ch = c.create_channel
294
- q = ch.queue("", :exclusive => true)
295
- q.subscribe do |_, _, _|
296
- delivered = true
297
- end
298
- close_all_connections!
299
- sleep 0.1
300
- c.should_not be_open
301
-
302
- wait_for_recovery
303
- ch.should be_open
304
-
305
- q.publish("")
306
- sleep 0.5
307
- expect(delivered).to be_true
383
+ qs.each do |q|
384
+ ch.queue_declare(q.name, passive: true)
308
385
  end
309
386
  end
387
+ end
310
388
 
311
- it "recovers all consumers" do
312
- n = 1024
313
-
314
- with_open do |c|
315
- ch = c.create_channel
316
- q = ch.queue("", :exclusive => true)
317
- n.times do
318
- q.subscribe do |_, _, _|
319
- delivered = true
320
- end
321
- end
322
- close_all_connections!
323
- sleep 0.1
324
- c.should_not be_open
325
-
326
- wait_for_recovery
327
- ch.should be_open
328
-
329
- q.consumer_count.should == n
330
- end
389
+ def exchange_names_in_vhost(vhost)
390
+ http_client.list_exchanges(vhost).map {|e| e["name"]}
391
+ end
392
+
393
+ def connections
394
+ http_client.list_connections
395
+ end
396
+
397
+ def channels
398
+ http_client.list_channels
399
+ end
400
+
401
+ def queue_names
402
+ http_client.list_queues.map {|q| q["name"]}
403
+ end
404
+
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)
331
418
  end
419
+ end
332
420
 
333
- it "recovers all queues" do
334
- n = 256
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
335
426
 
336
- qs = []
427
+ def wait_for_recovery_with(&probe)
428
+ poll_until &probe
429
+ end
337
430
 
338
- with_open do |c|
339
- ch = c.create_channel
431
+ def poll_while(&probe)
432
+ Bunny::TestKit.poll_while(&probe)
433
+ end
340
434
 
341
- n.times do
342
- qs << ch.queue("", :exclusive => true)
343
- end
344
- close_all_connections!
345
- sleep 0.1
346
- c.should_not be_open
435
+ def poll_until(&probe)
436
+ Bunny::TestKit.poll_until(&probe)
437
+ end
347
438
 
348
- wait_for_recovery
349
- ch.should be_open
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
350
447
 
351
- qs.each do |q|
352
- ch.queue_declare(q.name, :passive => true)
353
- end
354
- end
355
- end
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
356
482
  end
357
483
  end