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, "#delete" 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
@@ -21,7 +21,7 @@ describe Bunny::Exchange, "#delete" do
21
21
  # no exception as of RabbitMQ 3.2. MK.
22
22
  x.delete
23
23
 
24
- ch.exchanges.size.should == 0
24
+ expect(ch.exchanges.size).to eq 0
25
25
  end
26
26
  end
27
27
 
@@ -43,7 +43,7 @@ describe Bunny::Exchange, "#delete" do
43
43
  ch = connection.create_channel
44
44
  x = ch.direct('amq.direct')
45
45
 
46
- x.delete.should == nil
46
+ expect(x.delete).to eq nil
47
47
  end
48
48
  end
49
49
 
@@ -52,7 +52,7 @@ describe Bunny::Exchange, "#delete" do
52
52
  ch = connection.create_channel
53
53
  x = ch.fanout('amq.fanout')
54
54
 
55
- x.delete.should == nil
55
+ expect(x.delete).to eq nil
56
56
  end
57
57
  end
58
58
 
@@ -61,7 +61,7 @@ describe Bunny::Exchange, "#delete" do
61
61
  ch = connection.create_channel
62
62
  x = ch.topic('amq.topic')
63
63
 
64
- x.delete.should == nil
64
+ expect(x.delete).to eq nil
65
65
  end
66
66
  end
67
67
 
@@ -70,7 +70,7 @@ describe Bunny::Exchange, "#delete" do
70
70
  ch = connection.create_channel
71
71
  x = ch.headers('amq.headers')
72
72
 
73
- x.delete.should == nil
73
+ expect(x.delete).to eq nil
74
74
  end
75
75
  end
76
76
 
@@ -79,7 +79,7 @@ describe Bunny::Exchange, "#delete" do
79
79
  ch = connection.create_channel
80
80
  x = ch.headers('amq.match')
81
81
 
82
- x.delete.should == nil
82
+ expect(x.delete).to eq nil
83
83
  end
84
84
  end
85
85
 
@@ -89,16 +89,16 @@ describe Bunny::Exchange, "#delete" do
89
89
  it "returns true" do
90
90
  ch = connection.create_channel
91
91
 
92
- connection.exchange_exists?("amq.fanout").should be_true
93
- connection.exchange_exists?("amq.direct").should be_true
94
- connection.exchange_exists?("amq.topic").should be_true
95
- connection.exchange_exists?("amq.match").should be_true
92
+ expect(connection.exchange_exists?("amq.fanout")).to eq true
93
+ expect(connection.exchange_exists?("amq.direct")).to eq true
94
+ expect(connection.exchange_exists?("amq.topic")).to eq true
95
+ expect(connection.exchange_exists?("amq.match")).to eq true
96
96
  end
97
97
  end
98
98
 
99
99
  context "when a exchange DOES NOT exist" do
100
100
  it "returns false" do
101
- connection.exchange_exists?("suf89u9a4jo3ndnakls##{Time.now.to_i}").should be_false
101
+ expect(connection.exchange_exists?("suf89u9a4jo3ndnakls##{Time.now.to_i}")).to eq false
102
102
  end
103
103
  end
104
104
  end
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Bunny::Exchange 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
@@ -17,21 +17,21 @@ describe Bunny::Exchange do
17
17
  source = ch.fanout("bunny.exchanges.source#{rand}")
18
18
  destination = ch.fanout("bunny.exchanges.destination#{rand}")
19
19
 
20
- queue = ch.queue("", :exclusive => true)
20
+ queue = ch.queue("", exclusive: true)
21
21
  queue.bind(destination)
22
22
 
23
23
  destination.bind(source)
24
24
  source.publish("")
25
25
  sleep 0.5
26
26
 
27
- queue.message_count.should be == 1
28
- queue.pop(:ack => true)
27
+ expect(queue.message_count).to eq 1
28
+ queue.pop(manual_ack: true)
29
29
 
30
30
  destination.unbind(source)
31
31
  source.publish("")
32
32
  sleep 0.5
33
33
 
34
- queue.message_count.should be == 0
34
+ expect(queue.message_count).to eq 0
35
35
 
36
36
  source.delete
37
37
  destination.delete
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Bunny::Queue 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
@@ -14,13 +14,13 @@ describe Bunny::Queue do
14
14
  it "is closed when the connection it was declared on is closed" do
15
15
  ch1 = connection.create_channel
16
16
  ch2 = connection.create_channel
17
- q = ch1.queue("", :exclusive => true)
17
+ q = ch1.queue("", exclusive: true)
18
18
 
19
- ch1.queue_declare(q.name, :passive => true)
20
- ch2.queue_declare(q.name, :passive => true)
19
+ ch1.queue_declare(q.name, passive: true)
20
+ ch2.queue_declare(q.name, passive: true)
21
21
 
22
22
  ch1.close
23
- ch2.queue_declare(q.name, :passive => true)
23
+ ch2.queue_declare(q.name, passive: true)
24
24
 
25
25
  ch2.queue_delete(q.name)
26
26
  ch2.close
@@ -1,23 +1,41 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Client-defined heartbeat interval" do
4
- let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :heartbeat_interval => 4)
6
- c.start
7
- c
4
+ context "with value > 0" do
5
+ let(:connection) do
6
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 4)
7
+ c.start
8
+ c
9
+ end
10
+
11
+ it "can be enabled explicitly" do
12
+ sleep 5.0
13
+
14
+ connection.close
15
+ end
8
16
  end
9
17
 
10
- it "can be enabled explicitly" do
11
- sleep 5.0
18
+ # issue 267
19
+ context "with value = 0" do
20
+ let(:connection) do
21
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed",
22
+ heartbeat_timeout: 0, automatically_recover: false)
23
+ c.start
24
+ c
25
+ end
12
26
 
13
- connection.close
27
+ it "disables heartbeats" do
28
+ sleep 1.0
29
+
30
+ connection.close
31
+ end
14
32
  end
15
33
  end
16
34
 
17
35
 
18
36
  describe "Server-defined heartbeat interval" do
19
37
  let(:connection) do
20
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :heartbeat_interval => :server)
38
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: :server)
21
39
  c.start
22
40
  c
23
41
  end
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe Bunny::Queue, "#subscribe" 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
@@ -20,8 +20,8 @@ describe Bunny::Queue, "#subscribe" do
20
20
 
21
21
  t = Thread.new do
22
22
  ch = connection.create_channel
23
- q = ch.queue(queue_name, :auto_delete => true, :durable => false)
24
- q.subscribe(:exclusive => true, :ack => false) do |delivery_info, properties, payload|
23
+ q = ch.queue(queue_name, auto_delete: true, durable: false)
24
+ q.subscribe(exclusive: true, manual_ack: false) do |delivery_info, properties, payload|
25
25
  metadata = properties
26
26
  envelope = delivery_info
27
27
  end
@@ -32,63 +32,63 @@ describe Bunny::Queue, "#subscribe" do
32
32
  ch = connection.create_channel
33
33
  x = ch.default_exchange
34
34
  x.publish("hello",
35
- :routing_key => queue_name,
36
- :app_id => "bunny.example",
37
- :priority => 8,
38
- :type => "kinda.checkin",
35
+ routing_key: queue_name,
36
+ app_id: "bunny.example",
37
+ priority: 8,
38
+ type: "kinda.checkin",
39
39
  # headers table keys can be anything
40
- :headers => {
41
- :coordinates => {
42
- :latitude => 59.35,
43
- :longitude => 18.066667
40
+ headers: {
41
+ coordinates: {
42
+ latitude: 59.35,
43
+ longitude: 18.066667
44
44
  },
45
- :time => @now,
46
- :participants => 11,
47
- :venue => "Stockholm",
48
- :true_field => true,
49
- :false_field => false,
50
- :nil_field => nil,
51
- :ary_field => ["one", 2.0, 3, [{"abc" => 123}]]
45
+ time: @now,
46
+ participants: 11,
47
+ venue: "Stockholm",
48
+ true_field: true,
49
+ false_field: false,
50
+ nil_field: nil,
51
+ ary_field: ["one", 2.0, 3, [{"abc" => 123}]]
52
52
  },
53
- :timestamp => @now.to_i,
54
- :reply_to => "a.sender",
55
- :correlation_id => "r-1",
56
- :message_id => "m-1")
53
+ timestamp: @now.to_i,
54
+ reply_to: "a.sender",
55
+ correlation_id: "r-1",
56
+ message_id: "m-1")
57
57
 
58
58
  sleep 0.7
59
59
 
60
- metadata.content_type.should == "application/octet-stream"
61
- metadata.priority.should == 8
60
+ expect(metadata.content_type).to eq "application/octet-stream"
61
+ expect(metadata.priority).to eq 8
62
62
 
63
63
  time = metadata.headers["time"]
64
- time.year.should == @now.year
65
- time.month.should == @now.month
66
- time.day.should == @now.day
67
- time.hour.should == @now.hour
68
- time.min.should == @now.min
69
- time.sec.should == @now.sec
64
+ expect(time.year).to eq @now.year
65
+ expect(time.month).to eq @now.month
66
+ expect(time.day).to eq @now.day
67
+ expect(time.hour).to eq @now.hour
68
+ expect(time.min).to eq @now.min
69
+ expect(time.sec).to eq @now.sec
70
70
 
71
- metadata.headers["coordinates"]["latitude"].should == 59.35
72
- metadata.headers["participants"].should == 11
73
- metadata.headers["venue"].should == "Stockholm"
74
- metadata.headers["true_field"].should == true
75
- metadata.headers["false_field"].should == false
76
- metadata.headers["nil_field"].should be_nil
77
- metadata.headers["ary_field"].should == ["one", 2.0, 3, [{ "abc" => 123}]]
71
+ expect(metadata.headers["coordinates"]["latitude"]).to eq 59.35
72
+ expect(metadata.headers["participants"]).to eq 11
73
+ expect(metadata.headers["venue"]).to eq "Stockholm"
74
+ expect(metadata.headers["true_field"]).to eq true
75
+ expect(metadata.headers["false_field"]).to eq false
76
+ expect(metadata.headers["nil_field"]).to be_nil
77
+ expect(metadata.headers["ary_field"]).to eq ["one", 2.0, 3, [{ "abc" => 123}]]
78
78
 
79
- metadata.timestamp.should == Time.at(@now.to_i)
80
- metadata.type.should == "kinda.checkin"
81
- metadata.reply_to.should == "a.sender"
82
- metadata.correlation_id.should == "r-1"
83
- metadata.message_id.should == "m-1"
84
- metadata.app_id.should == "bunny.example"
79
+ expect(metadata.timestamp).to eq Time.at(@now.to_i)
80
+ expect(metadata.type).to eq "kinda.checkin"
81
+ expect(metadata.reply_to).to eq "a.sender"
82
+ expect(metadata.correlation_id).to eq "r-1"
83
+ expect(metadata.message_id).to eq "m-1"
84
+ expect(metadata.app_id).to eq "bunny.example"
85
85
 
86
- envelope.consumer_tag.should_not be_nil
87
- envelope.consumer_tag.should_not be_empty
88
- envelope.should_not be_redelivered
89
- envelope.delivery_tag.should == 1
90
- envelope.routing_key.should == queue_name
91
- envelope.exchange.should == ""
86
+ expect(envelope.consumer_tag).not_to be_nil
87
+ expect(envelope.consumer_tag).not_to be_empty
88
+ expect(envelope).not_to be_redelivered
89
+ expect(envelope.delivery_tag).to eq 1
90
+ expect(envelope.routing_key).to eq queue_name
91
+ expect(envelope.exchange).to eq ""
92
92
 
93
93
  ch.close
94
94
  end
@@ -2,7 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe "amq.* exchanges" 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
@@ -16,7 +16,7 @@ describe "amq.* exchanges" do
16
16
 
17
17
  ["amq.fanout", "amq.direct", "amq.topic", "amq.match", "amq.headers"].each do |e|
18
18
  x = ch.exchange(e)
19
- x.should be_predeclared
19
+ expect(x).to be_predeclared
20
20
  end
21
21
 
22
22
  ch.close
@@ -1,77 +1,191 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Bunny::Channel do
4
- let(:connection) do
5
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :continuation_timeout => 10000)
6
- c.start
7
- c
8
- end
9
-
10
4
  after :each do
11
5
  connection.close if connection.open?
12
6
  end
13
7
 
14
8
  let(:n) { 200 }
15
9
 
16
- context "when publishing with confirms enabled" do
17
- it "increments delivery index" do
18
- ch = connection.create_channel
19
- ch.should_not be_using_publisher_confirmations
10
+ shared_examples "publish confirms" do
11
+ context "when publishing with confirms enabled" do
12
+ it "increments delivery index" do
13
+ ch = connection.create_channel
14
+ expect(ch).not_to be_using_publisher_confirmations
15
+
16
+ ch.confirm_select
17
+ expect(ch).to be_using_publisher_confirmations
18
+
19
+ q = ch.queue("", exclusive: true)
20
+ x = ch.default_exchange
20
21
 
21
- ch.confirm_select
22
- ch.should be_using_publisher_confirmations
22
+ n.times do
23
+ x.publish("xyzzy", routing_key: q.name)
24
+ end
23
25
 
24
- q = ch.queue("", :exclusive => true)
25
- x = ch.default_exchange
26
+ expect(ch.next_publish_seq_no).to eq n + 1
27
+ expect(ch.wait_for_confirms).to eq true
28
+ sleep 0.25
26
29
 
27
- n.times do
28
- x.publish("xyzzy", :routing_key => q.name)
30
+ expect(q.message_count).to eq n
31
+ q.purge
32
+
33
+ ch.close
29
34
  end
30
35
 
31
- ch.next_publish_seq_no.should == n + 1
32
- ch.wait_for_confirms.should be_true
33
- sleep 0.25
36
+ describe "#wait_for_confirms" do
37
+ it "should not hang when all the publishes are confirmed" do
38
+ ch = connection.create_channel
39
+ expect(ch).not_to be_using_publisher_confirmations
40
+
41
+ ch.confirm_select
42
+ expect(ch).to be_using_publisher_confirmations
43
+
44
+ q = ch.queue("", exclusive: true)
45
+ x = ch.default_exchange
46
+
47
+ n.times do
48
+ x.publish("xyzzy", routing_key: q.name)
49
+ end
50
+
51
+ expect(ch.next_publish_seq_no).to eq n + 1
52
+ expect(ch.wait_for_confirms).to eq true
53
+
54
+ sleep 0.25
55
+
56
+ expect {
57
+ Bunny::Timeout.timeout(2) do
58
+ expect(ch.wait_for_confirms).to eq true
59
+ end
60
+ }.not_to raise_error
61
+
62
+ end
63
+
64
+ it "raises an error when called on a closed channel" do
65
+ ch = connection.create_channel
66
+
67
+ ch.confirm_select
68
+
69
+ ch.close
70
+
71
+ expect {
72
+ ch.wait_for_confirms
73
+ }.to raise_error(Bunny::ChannelAlreadyClosed)
74
+ end
75
+ end
76
+
77
+ context "when some of the messages get nacked" do
78
+ it "puts the nacks in the nacked_set" do
79
+ ch = connection.create_channel
80
+ expect(ch).not_to be_using_publisher_confirmations
81
+
82
+ ch.confirm_select
83
+ expect(ch).to be_using_publisher_confirmations
84
+
85
+ q = ch.queue("", exclusive: true)
86
+ x = ch.default_exchange
87
+
88
+ n.times do
89
+ x.publish("xyzzy", routing_key: q.name)
90
+ end
91
+
92
+ #be sneaky to simulate a nack
93
+ nacked_tag = nil
94
+ ch.instance_variable_get(:@unconfirmed_set_mutex).synchronize do
95
+ expect(ch.unconfirmed_set).to_not be_empty
96
+ nacked_tag = ch.unconfirmed_set.reduce(ch.next_publish_seq_no - 1) { |lowest, i| i < lowest ? i : lowest }
97
+ ch.handle_ack_or_nack(nacked_tag, false, true)
98
+ end
34
99
 
35
- q.message_count.should == n
36
- q.purge
100
+ expect(ch.nacked_set).not_to be_empty
101
+ expect(ch.nacked_set).to include(nacked_tag)
37
102
 
38
- ch.close
103
+ expect(ch.next_publish_seq_no).to eq n + 1
104
+ expect(ch.wait_for_confirms).to eq false
105
+
106
+ expect(ch.nacked_set).not_to be_empty
107
+ expect(ch.nacked_set).to include(nacked_tag)
108
+
109
+ sleep 0.25
110
+ expect(q.message_count).to eq n
111
+ q.purge
112
+
113
+ ch.close
114
+ end
115
+ end
39
116
  end
40
117
  end
41
118
 
42
-
43
- context "with a single-threaded connection" do
119
+ context "with a multi-threaded connection" do
44
120
  let(:connection) do
45
- c = Bunny.new(:user => "bunny_gem", :password => "bunny_password", :vhost => "bunny_testbed", :continuation_timeout => 10000, :threaded => false)
121
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", continuation_timeout: 10000)
46
122
  c.start
47
123
  c
48
124
  end
49
125
 
126
+ include_examples "publish confirms"
50
127
 
51
- context "when publishing with confirms enabled" do
52
- it "increments delivery index" do
53
- ch = connection.create_channel
54
- ch.should_not be_using_publisher_confirmations
55
-
56
- ch.confirm_select
57
- ch.should be_using_publisher_confirmations
128
+ it "returns only when all confirmations for publishes are received" do
129
+ ch = connection.create_channel
58
130
 
59
- q = ch.queue("", :exclusive => true)
60
- x = ch.default_exchange
131
+ operations_log = []
132
+ operations_log_mutex = Mutex.new
133
+ acks_received = Queue.new
61
134
 
62
- n.times do
63
- x.publish("xyzzy", :routing_key => q.name)
135
+ log_acks = proc do |tag, _, is_nack|
136
+ operations_log_mutex.synchronize do
137
+ operation = "#{'n' if is_nack}ack_#{tag}"
138
+ operations_log << operation unless operations_log.include?(operation)
64
139
  end
140
+ acks_received << true
141
+ end
65
142
 
66
- ch.next_publish_seq_no.should == n + 1
67
- ch.wait_for_confirms.should be_true
68
- sleep 0.25
143
+ ch.confirm_select(log_acks)
69
144
 
70
- q.message_count.should == n
71
- q.purge
145
+ x = ch.default_exchange
146
+ q = ch.temporary_queue
72
147
 
73
- ch.close
148
+ x.publish('msg', routing_key: q.name)
149
+
150
+ # wait for the confirmation to arrive
151
+ acks_received.pop
152
+
153
+ # artificially simulate a slower ack. the test should work properly even
154
+ # without this patch, but it's here just to be sure we catch it.
155
+ def (x.channel).handle_ack_or_nack(delivery_tag_before_offset, multiple, nack)
156
+ sleep 0.1
157
+ super
74
158
  end
159
+
160
+ x.publish('msg', routing_key: q.name)
161
+ x.publish('msg', routing_key: q.name)
162
+
163
+ if x.wait_for_confirms
164
+ operations_log_mutex.synchronize do
165
+ operations_log << 'all_confirmed'
166
+ end
167
+ end
168
+
169
+ # wait for all the confirmations to arrive
170
+ acks_received.pop
171
+ acks_received.pop
172
+
173
+ expect(operations_log).to eq([
174
+ 'ack_1',
175
+ 'ack_2',
176
+ 'ack_3',
177
+ 'all_confirmed',
178
+ ])
75
179
  end
76
180
  end
181
+
182
+ context "with a single-threaded connection" do
183
+ let(:connection) do
184
+ c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", continuation_timeout: 10000, threaded: false)
185
+ c.start
186
+ c
187
+ end
188
+
189
+ include_examples "publish confirms"
190
+ end
77
191
  end