bunny 2.11.0 → 2.12.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 30bc96b65edcb9a0d976408669ca1af4654e5a8a
4
- data.tar.gz: 144a71c34e3f0cc04024dd7d80eb400fd1834a36
2
+ SHA256:
3
+ metadata.gz: 80a243c18a410ace3d56439bf7cbb60141112e388db773e7b3fe1bbe6594ba30
4
+ data.tar.gz: 548aa57383b638a79a2b4863255c8235304b498a850204977ed965118d9131c3
5
5
  SHA512:
6
- metadata.gz: f85596d0dd8e731f6d6bf6719d3f1324690762c61f2829ad65125a4b55d833c000b574f90160e956fefbff930519e9d33fab30723cb9b0a00de85e3252a208ae
7
- data.tar.gz: 60ee0016f188410417f8742c24850fa57260808759c2c95f71f4c9ea2ea2cf9e940ea170b4503f6bca800f7ccf0cae5eabe382ebf00dbd432ad6337e64febcad
6
+ metadata.gz: fc92d15fa248ed121180d78db848b4b8399104e4d72aea6cbadca4a14ece080cd893a6db75b8c29fc02c002510ad15a0949492ce19062d8bb4b78e90d84500f9
7
+ data.tar.gz: 7cc116eec7ae1119d8216853af7f266e202756b983b910ae56c129ab0b2dbd93bb55213c2e682e56c3e56c5aac98ba2e04e92b57873702c51e358f0975112929
@@ -90,7 +90,7 @@ Version >= 1.6.0+ is required for compose version 2 syntax.
90
90
  After those have been installed (and the `docker-compose` command is available on your command line path), run
91
91
 
92
92
  ```
93
- docker-compose up
93
+ docker-compose build && docker-compose up
94
94
  ```
95
95
 
96
96
  The first time you do this, it will take some time, since it has to download everything it needs
@@ -1,4 +1,64 @@
1
- ## Changes between Bunny 2.10.0 and 2.11.0 (unreleased)
1
+ ## Changes between Bunny 2.11.0 and 2.12.0 (unreleased)
2
+
3
+ ### More Defensive Treatment of `queue.declare-ok` Responses
4
+
5
+ Responses for `queue.declare` are now checked against a memoized
6
+ queue name (but only if the queue is not server-named). This helps
7
+ avoids scenarios with overlapping/concurrent requests due to high
8
+ network latency as demonstrated in [#558](https://github.com/ruby-amqp/bunny/issues/558).
9
+
10
+ "Mismatched" responses will be ignored: Bunny channel API would throw
11
+ an exception for such declarations and there would be no way to "return to"
12
+ even if a matching response arrived and was matched with one of the pending
13
+ requests in a reasonable period of time.
14
+
15
+ As part of this work a new Toxiproxy-based test suite was introduced
16
+ to Bunny.
17
+
18
+ GitHub issue: [#558](https://github.com/ruby-amqp/bunny/issues/558)
19
+
20
+ Reproduction steps contributed by Brian Morton and Scott Bonebraker.
21
+
22
+ ### I/O Exceptions from Heartbeat Sender are Now Silent
23
+
24
+ Heartbeat sender's purpose is to notify the peer, not so much
25
+ to detect local connectivity failures; those will be detected
26
+ by the I/O loop and transport.
27
+
28
+ For single threaded connection users that prefer to roll their own
29
+ recovery strategies getting exceptions from the heartbeat sender
30
+ was counterproductive and painful to deal with.
31
+
32
+ As part of this work a new Toxiproxy-based test suite was introduced
33
+ to Bunny.
34
+
35
+ GitHub issue: [#559](https://github.com/ruby-amqp/bunny/issues/559)
36
+
37
+ Contributed by Scott Bonebraker.
38
+
39
+ ### Connection Recovery Will Fail When Max Retry Attempt Limit is Exceeded
40
+
41
+ GitHub issue: [#549](https://github.com/ruby-amqp/bunny/issues/549)
42
+
43
+ Contributed by Arlandis Word.
44
+
45
+ ### Squashed Warnings
46
+
47
+ Many warnings have been eliminated.
48
+
49
+ GitHub issue: [#563](https://github.com/ruby-amqp/bunny/issues/563)
50
+
51
+ Contributed by @dacto.
52
+
53
+
54
+ ### API Reference Corrections
55
+
56
+ GitHub issue: [#557](https://github.com/ruby-amqp/bunny/pull/557)
57
+
58
+ Contributed by Bruno Costa.
59
+
60
+
61
+ ## Changes between Bunny 2.10.0 and 2.11.0 (Jun 21st, 2018)
2
62
 
3
63
  ### More Reliable System-wide Trusted Certificate Directory Detection
4
64
 
@@ -17,6 +77,7 @@ Contributed by Ana María Martínez Gómez.
17
77
  `2.10.0` is a maintenance release that introduces a couple of
18
78
  **minor potentially breaking changes**.
19
79
 
80
+
20
81
  ### Disabling Heartbeats Also Disables TCP Socket Read Timeouts
21
82
 
22
83
  Disabling heartbeats will now disable TCP socket read timeouts.
data/Gemfile CHANGED
@@ -11,7 +11,7 @@ extend Module.new {
11
11
 
12
12
  local_path = File.expand_path("../vendor/#{name}", __FILE__)
13
13
  if File.exist?(local_path)
14
- super name, options.merge(:path => local_path).
14
+ super name, options.merge(path: local_path).
15
15
  delete_if { |key, _| [:git, :branch].include?(key) }
16
16
  else
17
17
  super name, *args
@@ -19,21 +19,24 @@ extend Module.new {
19
19
  end
20
20
  }
21
21
 
22
- gem "rake", ">= 10.0.4"
22
+ gem "rake", ">= 12.3.1"
23
23
  gem "effin_utf8"
24
24
 
25
25
  group :development do
26
26
  gem "yard"
27
27
 
28
- gem "redcarpet", :platform => :mri
29
- gem "ruby-prof", :platform => :mri
28
+ gem "redcarpet", platform: :mri
29
+ gem "ruby-prof", platform: :mri
30
30
 
31
- gem "json", :platform => :ruby_18
31
+ gem "ripl"
32
+ gem "ripl-multi_line"
33
+ gem "ripl-irb"
32
34
  end
33
35
 
34
36
  group :test do
35
37
  gem "rspec", "~> 3.5.0"
36
38
  gem "rabbitmq_http_api_client", "~> 1.9.1", require: "rabbitmq/http/client"
39
+ gem "toxiproxy", "~> 1.0.3"
37
40
  end
38
41
 
39
42
  gemspec
@@ -44,7 +47,7 @@ def custom_gem(name, options = Hash.new)
44
47
  local_path = File.expand_path("../vendor/#{name}", __FILE__)
45
48
  if File.exist?(local_path)
46
49
  puts "Using #{name} from #{local_path}..."
47
- gem name, options.merge(:path => local_path).delete_if { |key, _| [:git, :branch].include?(key) }
50
+ gem name, options.merge(path: local_path).delete_if { |key, _| [:git, :branch].include?(key) }
48
51
  else
49
52
  gem name, options
50
53
  end
data/README.md CHANGED
@@ -71,8 +71,9 @@ a stable public API.
71
71
  Change logs per release series:
72
72
 
73
73
  * [master](https://github.com/ruby-amqp/bunny/blob/master/ChangeLog.md)
74
+ * [2.11.x](https://github.com/ruby-amqp/bunny/blob/2.11.x-stable/ChangeLog.md)
75
+ * [2.10.x](https://github.com/ruby-amqp/bunny/blob/2.10.x-stable/ChangeLog.md)
74
76
  * [2.9.x](https://github.com/ruby-amqp/bunny/blob/2.9.x-stable/ChangeLog.md)
75
- * [2.8.x](https://github.com/ruby-amqp/bunny/blob/2.8.x-stable/ChangeLog.md)
76
77
 
77
78
 
78
79
 
@@ -95,7 +96,7 @@ gem install bunny
95
96
  To use Bunny in a project managed with Bundler:
96
97
 
97
98
  ``` ruby
98
- gem "bunny", ">= 2.10.0"
99
+ gem "bunny", ">= 2.11.0"
99
100
  ```
100
101
 
101
102
 
@@ -24,10 +24,9 @@ Gem::Specification.new do |s|
24
24
  s.email = ["michael.s.klishin@gmail.com"]
25
25
 
26
26
  # Dependencies
27
- s.add_dependency "amq-protocol", "~> 2.3.0"
27
+ s.add_runtime_dependency 'amq-protocol', '~> 2.3', '>= 2.3.0'
28
28
 
29
29
  # Files.
30
- s.has_rdoc = true
31
30
  s.extra_rdoc_files = ["README.md"]
32
31
  s.files = `git ls-files`.split("\n")
33
32
  s.test_files = `git ls-files -- spec/*`.split("\n")
@@ -17,4 +17,12 @@ services:
17
17
  - 5671-5672:5671-5672
18
18
  - 15672:15672
19
19
  volumes:
20
- - ./spec:/spec:ro
20
+ - ./spec:/spec:ro
21
+ toxiproxy:
22
+ container_name: toxiproxy
23
+ image: shopify/toxiproxy
24
+ ports:
25
+ - 8474:8474
26
+ - 11111:11111
27
+ depends_on:
28
+ - rabbitmq
@@ -1,16 +1,16 @@
1
- FROM debian:jessie
1
+ FROM ubuntu:bionic
2
2
 
3
3
  RUN apt-get -q update && \
4
- apt-get install -yq --no-install-recommends wget ca-certificates
4
+ apt-get install -yq --no-install-recommends gnupg1 wget ca-certificates apt-transport-https
5
5
 
6
- RUN echo 'deb http://www.rabbitmq.com/debian/ testing main' > /etc/apt/sources.list.d/rabbitmq.list && \
7
- wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | apt-key add -
6
+ RUN echo 'deb https://dl.bintray.com/rabbitmq/debian bionic main erlang' > /etc/apt/sources.list.d/rabbitmq.list && \
7
+ wget -O - 'https://dl.bintray.com/rabbitmq/Keys/rabbitmq-release-signing-key.asc' | apt-key add -
8
8
 
9
9
  RUN apt-get -q update && \
10
- apt-get install -yq --no-install-recommends rabbitmq-server
10
+ apt-get install -yq rabbitmq-server
11
11
 
12
12
  COPY docker-entrypoint.sh /
13
13
 
14
14
  ENTRYPOINT /docker-entrypoint.sh
15
15
 
16
- EXPOSE 5671 5672 15672
16
+ EXPOSE 5671 5672 15672
@@ -265,15 +265,6 @@ module Bunny
265
265
  @status == :closed
266
266
  end
267
267
 
268
- def to_s
269
- oid = ("0x%x" % (self.object_id << 1))
270
- "<#{self.class.name}:#{oid} number=#{@channel.id} @open=#{open?} connection=#{@connection.to_s}>"
271
- end
272
-
273
- def inspect
274
- to_s
275
- end
276
-
277
268
  #
278
269
  # @group Backwards compatibility with 0.8.0
279
270
  #
@@ -657,7 +648,7 @@ module Bunny
657
648
 
658
649
  @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
659
650
 
660
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
651
+ with_continuation_timeout do
661
652
  @last_basic_qos_ok = wait_on_continuations
662
653
  end
663
654
  raise_if_continuation_resulted_in_a_channel_error!
@@ -678,7 +669,7 @@ module Bunny
678
669
  raise_if_no_longer_open!
679
670
 
680
671
  @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
681
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
672
+ with_continuation_timeout do
682
673
  @last_basic_recover_ok = wait_on_continuations
683
674
  end
684
675
  raise_if_continuation_resulted_in_a_channel_error!
@@ -885,7 +876,7 @@ module Bunny
885
876
  arguments))
886
877
 
887
878
  begin
888
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
879
+ with_continuation_timeout do
889
880
  @last_basic_consume_ok = wait_on_continuations
890
881
  end
891
882
  rescue Exception => e
@@ -935,7 +926,7 @@ module Bunny
935
926
  consumer.arguments))
936
927
 
937
928
  begin
938
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
929
+ with_continuation_timeout do
939
930
  @last_basic_consume_ok = wait_on_continuations
940
931
  end
941
932
  rescue Exception => e
@@ -970,7 +961,7 @@ module Bunny
970
961
  def basic_cancel(consumer_tag)
971
962
  @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
972
963
 
973
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
964
+ with_continuation_timeout do
974
965
  @last_basic_cancel_ok = wait_on_continuations
975
966
  end
976
967
 
@@ -994,7 +985,8 @@ module Bunny
994
985
 
995
986
  # Declares a queue using queue.declare AMQP 0.9.1 method.
996
987
  #
997
- # @param [String] name Queue name
988
+ # @param [String] name The name of the queue or an empty string to let RabbitMQ generate a name.
989
+ # Note that LF and CR characters will be stripped from the value.
998
990
  # @param [Hash] opts Queue properties
999
991
  #
1000
992
  # @option opts [Boolean] durable (false) Should information about this queue be persisted to disk so that it
@@ -1012,16 +1004,28 @@ module Bunny
1012
1004
  def queue_declare(name, opts = {})
1013
1005
  raise_if_no_longer_open!
1014
1006
 
1015
- @connection.send_frame(AMQ::Protocol::Queue::Declare.encode(@id,
1016
- name,
1007
+ # strip trailing new line and carriage returns
1008
+ # just like RabbitMQ does
1009
+ safe_name = name.gsub(/[\r\n]/, "")
1010
+ @pending_queue_declare_name = safe_name
1011
+ @connection.send_frame(
1012
+ AMQ::Protocol::Queue::Declare.encode(@id,
1013
+ @pending_queue_declare_name,
1017
1014
  opts.fetch(:passive, false),
1018
1015
  opts.fetch(:durable, false),
1019
1016
  opts.fetch(:exclusive, false),
1020
1017
  opts.fetch(:auto_delete, false),
1021
1018
  false,
1022
1019
  opts[:arguments]))
1023
- @last_queue_declare_ok = wait_on_continuations
1024
1020
 
1021
+ begin
1022
+ with_continuation_timeout do
1023
+ @last_queue_declare_ok = wait_on_continuations
1024
+ end
1025
+ ensure
1026
+ # clear pending continuation context if it belongs to us
1027
+ @pending_queue_declare_name = nil if @pending_queue_declare_name == safe_name
1028
+ end
1025
1029
  raise_if_continuation_resulted_in_a_channel_error!
1026
1030
 
1027
1031
  @last_queue_declare_ok
@@ -1046,7 +1050,7 @@ module Bunny
1046
1050
  opts[:if_unused],
1047
1051
  opts[:if_empty],
1048
1052
  false))
1049
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1053
+ with_continuation_timeout do
1050
1054
  @last_queue_delete_ok = wait_on_continuations
1051
1055
  end
1052
1056
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1066,7 +1070,7 @@ module Bunny
1066
1070
 
1067
1071
  @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
1068
1072
 
1069
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1073
+ with_continuation_timeout do
1070
1074
  @last_queue_purge_ok = wait_on_continuations
1071
1075
  end
1072
1076
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1102,7 +1106,7 @@ module Bunny
1102
1106
  (opts[:routing_key] || opts[:key]),
1103
1107
  false,
1104
1108
  opts[:arguments]))
1105
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1109
+ with_continuation_timeout do
1106
1110
  @last_queue_bind_ok = wait_on_continuations
1107
1111
  end
1108
1112
 
@@ -1137,7 +1141,7 @@ module Bunny
1137
1141
  exchange_name,
1138
1142
  opts[:routing_key],
1139
1143
  opts[:arguments]))
1140
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1144
+ with_continuation_timeout do
1141
1145
  @last_queue_unbind_ok = wait_on_continuations
1142
1146
  end
1143
1147
 
@@ -1150,26 +1154,30 @@ module Bunny
1150
1154
 
1151
1155
  # @group Exchange operations (exchange.*)
1152
1156
 
1153
- # Declares a echange using echange.declare AMQP 0.9.1 method.
1157
+ # Declares a exchange using echange.declare AMQP 0.9.1 method.
1154
1158
  #
1155
- # @param [String] name Exchange name
1159
+ # @param [String] name The name of the exchange. Note that LF and CR characters
1160
+ # will be stripped from the value.
1156
1161
  # @param [String,Symbol] type Exchange type, e.g. :fanout or :topic
1157
1162
  # @param [Hash] opts Exchange properties
1158
1163
  #
1159
- # @option opts [Boolean] durable (false) Should information about this echange be persisted to disk so that it
1164
+ # @option opts [Boolean] durable (false) Should information about this exchange be persisted to disk so that it
1160
1165
  # can survive broker restarts? Typically set to true for long-lived exchanges.
1161
- # @option opts [Boolean] auto_delete (false) Should this echange be deleted when it is no longer used?
1166
+ # @option opts [Boolean] auto_delete (false) Should this exchange be deleted when it is no longer used?
1162
1167
  # @option opts [Boolean] passive (false) If true, exchange will be checked for existence. If it does not
1163
1168
  # exist, {Bunny::NotFound} will be raised.
1164
1169
  #
1165
1170
  # @return [AMQ::Protocol::Exchange::DeclareOk] RabbitMQ response
1166
- # @see http://rubybunny.info/articles/echanges.html Exchanges and Publishing guide
1171
+ # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide
1167
1172
  # @api public
1168
1173
  def exchange_declare(name, type, opts = {})
1169
1174
  raise_if_no_longer_open!
1170
1175
 
1176
+ # strip trailing new line and carriage returns
1177
+ # just like RabbitMQ does
1178
+ safe_name = name.gsub(/[\r\n]/, "")
1171
1179
  @connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id,
1172
- name,
1180
+ safe_name,
1173
1181
  type.to_s,
1174
1182
  opts.fetch(:passive, false),
1175
1183
  opts.fetch(:durable, false),
@@ -1177,7 +1185,7 @@ module Bunny
1177
1185
  opts.fetch(:internal, false),
1178
1186
  false, # nowait
1179
1187
  opts[:arguments]))
1180
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1188
+ with_continuation_timeout do
1181
1189
  @last_exchange_declare_ok = wait_on_continuations
1182
1190
  end
1183
1191
 
@@ -1202,7 +1210,7 @@ module Bunny
1202
1210
  name,
1203
1211
  opts[:if_unused],
1204
1212
  false))
1205
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1213
+ with_continuation_timeout do
1206
1214
  @last_exchange_delete_ok = wait_on_continuations
1207
1215
  end
1208
1216
 
@@ -1246,7 +1254,7 @@ module Bunny
1246
1254
  opts[:routing_key],
1247
1255
  false,
1248
1256
  opts[:arguments]))
1249
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1257
+ with_continuation_timeout do
1250
1258
  @last_exchange_bind_ok = wait_on_continuations
1251
1259
  end
1252
1260
 
@@ -1290,7 +1298,7 @@ module Bunny
1290
1298
  opts[:routing_key],
1291
1299
  false,
1292
1300
  opts[:arguments]))
1293
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1301
+ with_continuation_timeout do
1294
1302
  @last_exchange_unbind_ok = wait_on_continuations
1295
1303
  end
1296
1304
 
@@ -1318,7 +1326,7 @@ module Bunny
1318
1326
  raise_if_no_longer_open!
1319
1327
 
1320
1328
  @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
1321
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1329
+ with_continuation_timeout do
1322
1330
  @last_channel_flow_ok = wait_on_continuations
1323
1331
  end
1324
1332
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1339,7 +1347,7 @@ module Bunny
1339
1347
  raise_if_no_longer_open!
1340
1348
 
1341
1349
  @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
1342
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1350
+ with_continuation_timeout do
1343
1351
  @last_tx_select_ok = wait_on_continuations
1344
1352
  end
1345
1353
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1355,7 +1363,7 @@ module Bunny
1355
1363
  raise_if_no_longer_open!
1356
1364
 
1357
1365
  @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
1358
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1366
+ with_continuation_timeout do
1359
1367
  @last_tx_commit_ok = wait_on_continuations
1360
1368
  end
1361
1369
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1370,7 +1378,7 @@ module Bunny
1370
1378
  raise_if_no_longer_open!
1371
1379
 
1372
1380
  @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
1373
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1381
+ with_continuation_timeout do
1374
1382
  @last_tx_rollback_ok = wait_on_continuations
1375
1383
  end
1376
1384
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1417,7 +1425,7 @@ module Bunny
1417
1425
  @confirms_callback = callback
1418
1426
 
1419
1427
  @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
1420
- Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1428
+ with_continuation_timeout do
1421
1429
  @last_confirm_select_ok = wait_on_continuations
1422
1430
  end
1423
1431
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1589,7 +1597,11 @@ module Bunny
1589
1597
 
1590
1598
  # @return [String] Brief human-readable representation of the channel
1591
1599
  def to_s
1592
- "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}>"
1600
+ "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s}> @open=#{open?}"
1601
+ end
1602
+
1603
+ def inspect
1604
+ to_s
1593
1605
  end
1594
1606
 
1595
1607
 
@@ -1597,6 +1609,11 @@ module Bunny
1597
1609
  # Implementation
1598
1610
  #
1599
1611
 
1612
+ # @private
1613
+ def with_continuation_timeout(&block)
1614
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout, &block)
1615
+ end
1616
+
1600
1617
  # @private
1601
1618
  def register_consumer(consumer_tag, consumer)
1602
1619
  @consumer_mutex.synchronize do
@@ -1620,12 +1637,33 @@ module Bunny
1620
1637
  end
1621
1638
  end
1622
1639
 
1640
+ # @private
1641
+ def pending_server_named_queue_declaration?
1642
+ @pending_queue_declare_name && @pending_queue_declare_name.empty?
1643
+ end
1644
+
1645
+ # @private
1646
+ def can_accept_queue_declare_ok?(method)
1647
+ @pending_queue_declare_name == method.queue ||
1648
+ pending_server_named_queue_declaration?
1649
+ end
1650
+
1623
1651
  # @private
1624
1652
  def handle_method(method)
1625
1653
  @logger.debug { "Channel#handle_frame on channel #{@id}: #{method.inspect}" }
1626
1654
  case method
1627
1655
  when AMQ::Protocol::Queue::DeclareOk then
1628
- @continuations.push(method)
1656
+ # safeguard against late arrivals of responses and
1657
+ # so on, see ruby-amqp/bunny#558
1658
+ if can_accept_queue_declare_ok?(method)
1659
+ @continuations.push(method)
1660
+ else
1661
+ if !pending_server_named_queue_declaration?
1662
+ # this response is for an outdated/overwritten
1663
+ # queue.declare, drop it
1664
+ @logger.warn "Received a queue.declare-ok response for a mismatching queue (#{method.queue} instead of #{@pending_queue_declare_name}) on channel #{@id} possibly due to a timeout, ignoring it"
1665
+ end
1666
+ end
1629
1667
  when AMQ::Protocol::Queue::DeleteOk then
1630
1668
  @continuations.push(method)
1631
1669
  when AMQ::Protocol::Queue::PurgeOk then
@@ -25,6 +25,7 @@ module Bunny
25
25
  @shutdown_conditional = ::ConditionVariable.new
26
26
  @queue = ::Queue.new
27
27
  @paused = false
28
+ @running = false
28
29
  end
29
30
 
30
31
 
@@ -24,6 +24,11 @@ module Bunny
24
24
  [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
25
25
  end
26
26
 
27
+ def initialize(*args)
28
+ super
29
+ @__bunny_socket_eof_flag__ = false
30
+ end
31
+
27
32
  # Reads given number of bytes with an optional timeout
28
33
  #
29
34
  # @param [Integer] count How many bytes to read
@@ -29,6 +29,7 @@ module Bunny
29
29
  @interval = [(period / 2) - 1, 0.4].max
30
30
 
31
31
  @thread = Thread.new(&method(:run))
32
+ @thread.report_on_exception = false if @thread.respond_to?(:report_on_exception)
32
33
  end
33
34
  end
34
35
 
@@ -63,7 +64,7 @@ module Bunny
63
64
 
64
65
  if now > (@last_activity_time + @interval)
65
66
  @logger.debug { "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}" }
66
- @transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode)
67
+ @transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode, true)
67
68
  end
68
69
  end
69
70
  end
@@ -8,6 +8,11 @@ module Bunny
8
8
  # methods found in Bunny::Socket.
9
9
  class SSLSocket < Bunny::SSLSocket
10
10
 
11
+ def initialize(*args)
12
+ super
13
+ @__bunny_socket_eof_flag__ = false
14
+ end
15
+
11
16
  # Reads given number of bytes with an optional timeout
12
17
  #
13
18
  # @param [Integer] count How many bytes to read
@@ -375,11 +375,6 @@ module Bunny
375
375
 
376
376
  protected
377
377
 
378
- # @private
379
- def self.add_default_options(name, opts, block)
380
- { :queue => name, :nowait => (block.nil? && !name.empty?) }.merge(opts)
381
- end
382
-
383
378
  # @private
384
379
  def self.add_default_options(name, opts)
385
380
  # :nowait is always false for Bunny
@@ -16,6 +16,10 @@ module Bunny
16
16
  @logger = @session.logger
17
17
 
18
18
  @mutex = Mutex.new
19
+
20
+ @stopping = false
21
+ @stopped = false
22
+ @network_is_down = false
19
23
  end
20
24
 
21
25
 
@@ -78,7 +78,7 @@ module Bunny
78
78
 
79
79
  # @return [Bunny::Transport]
80
80
  attr_reader :transport
81
- attr_reader :status, :port, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded
81
+ attr_reader :status, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded
82
82
  attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
83
83
  attr_reader :channel_id_allocator
84
84
  # Authentication mechanism, e.g. "PLAIN" or "EXTERNAL"
@@ -152,15 +152,17 @@ module Bunny
152
152
  @addresses = self.addresses_from(opts)
153
153
  @address_index = 0
154
154
 
155
- # re-init, see above
156
- @logger = opts.fetch(:logger, init_default_logger(log_file, log_level))
157
-
155
+ @transport = nil
158
156
  @user = self.username_from(opts)
159
157
  @pass = self.password_from(opts)
160
158
  @vhost = self.vhost_from(opts)
161
159
  @threaded = opts.fetch(:threaded, true)
162
160
 
161
+ # re-init, see above
162
+ @logger = opts.fetch(:logger, init_default_logger(log_file, log_level))
163
+
163
164
  validate_connection_options(opts)
165
+ @last_connection_error = nil
164
166
 
165
167
  # should automatic recovery from network failures be used?
166
168
  @automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil?
@@ -188,6 +190,7 @@ module Bunny
188
190
  @client_channel_max = normalize_client_channel_max(opts.fetch(:channel_max, DEFAULT_CHANNEL_MAX))
189
191
  # will be-renegotiated during connection tuning steps. MK.
190
192
  @channel_max = @client_channel_max
193
+ @heartbeat_sender = nil
191
194
  @client_heartbeat = self.heartbeat_from(opts)
192
195
 
193
196
  client_props = opts[:properties] || opts[:client_properties] || {}
@@ -308,10 +311,6 @@ module Bunny
308
311
  @transport.post_initialize_socket
309
312
  @transport.connect
310
313
 
311
- if @socket_configurator
312
- @transport.configure_socket(&@socket_configurator)
313
- end
314
-
315
314
  self.init_connection
316
315
  self.open_connection
317
316
 
@@ -418,7 +417,7 @@ module Bunny
418
417
  @status_mutex.synchronize { @status == :closed }
419
418
  end
420
419
 
421
- # @return [Boolean] true if this AMQP 0.9.1 connection has been programmatically closed
420
+ # @return [Boolean] true if this AMQP 0.9.1 connection has been closed by the user (as opposed to the server)
422
421
  def manually_closed?
423
422
  @status_mutex.synchronize { @manually_closed == true }
424
423
  end
@@ -757,6 +756,7 @@ module Bunny
757
756
  end
758
757
  else
759
758
  @logger.error "Ran out of recovery attempts (limit set to #{@max_recovery_attempts})"
759
+ self.close
760
760
  end
761
761
  end
762
762
 
@@ -1078,9 +1078,13 @@ module Bunny
1078
1078
  # still recommend not sharing channels between threads except for consumer-only cases in the docs. MK.
1079
1079
  channel.synchronize do
1080
1080
  # see rabbitmq/rabbitmq-server#156
1081
- data = frames.reduce("") { |acc, frame| acc << frame.encode }
1082
- @transport.write(data)
1083
- signal_activity!
1081
+ if open?
1082
+ data = frames.reduce("") { |acc, frame| acc << frame.encode }
1083
+ @transport.write(data)
1084
+ signal_activity!
1085
+ else
1086
+ raise ConnectionClosedError.new(frames)
1087
+ end
1084
1088
  end
1085
1089
  end # send_frameset(frames)
1086
1090
 
@@ -1096,8 +1100,12 @@ module Bunny
1096
1100
  # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
1097
1101
  # locking. See a note about "single frame" methods in a comment in `send_frameset`. MK.
1098
1102
  channel.synchronize do
1099
- frames.each { |frame| self.send_frame_without_timeout(frame, false) }
1100
- signal_activity!
1103
+ if open?
1104
+ frames.each { |frame| self.send_frame_without_timeout(frame, false) }
1105
+ signal_activity!
1106
+ else
1107
+ raise ConnectionClosedError.new(frames)
1108
+ end
1101
1109
  end
1102
1110
  end # send_frameset_without_timeout(frames)
1103
1111
 
@@ -28,7 +28,6 @@ module Bunny
28
28
  attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
29
29
  attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path
30
30
 
31
- attr_writer :read_timeout
32
31
  def read_timeout=(v)
33
32
  @read_timeout = v
34
33
  @read_timeout = nil if @read_timeout == 0
@@ -58,6 +57,8 @@ module Bunny
58
57
 
59
58
  @writes_mutex = @session.mutex_impl.new
60
59
 
60
+ @socket = nil
61
+
61
62
  prepare_tls_context(opts) if @tls_enabled
62
63
  end
63
64
 
@@ -157,12 +158,13 @@ module Bunny
157
158
  end
158
159
 
159
160
  # Writes data to the socket without timeout checks
160
- def write_without_timeout(data)
161
+ def write_without_timeout(data, raise_exceptions = false)
161
162
  begin
162
163
  @writes_mutex.synchronize { @socket.write(data) }
163
164
  @socket.flush
164
165
  rescue SystemCallError, Bunny::ConnectionError, IOError => e
165
166
  close
167
+ raise e if raise_exceptions
166
168
 
167
169
  if @session.automatically_recover?
168
170
  @session.handle_network_failure(e)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "2.11.0"
5
+ VERSION = "2.12.0.rc1"
6
6
  end
data/repl CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/bin/sh
2
2
 
3
- bundle exec irb -Ilib -r'bunny'
3
+ bundle exec ripl -Ilib -r'bunny' -r'ripl/multi_line' -r'ripl/irb'
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+ require_relative "../../toxiproxy_helper"
3
+
4
+ if ::Toxiproxy.running?
5
+ describe Bunny::Channel, "#basic_publish" do
6
+ include RabbitMQ::Toxiproxy
7
+
8
+
9
+ after :each do
10
+ @connection.close if @connection.open?
11
+ end
12
+
13
+ context "when the the connection detects missed heartbeats with automatic recovery" do
14
+ before(:each) do
15
+ setup_toxiproxy
16
+ @connection = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed",
17
+ host: "localhost:11111", heartbeat_timeout: 1, automatically_recover: true)
18
+ @connection.start
19
+ end
20
+
21
+ let(:queue_name) { "bunny.basic.publish.queue#{rand}" }
22
+
23
+ it "raises a ConnectionClosedError" do
24
+ ch = @connection.create_channel
25
+ begin
26
+ rabbitmq_toxiproxy.down do
27
+ sleep 2
28
+ expect { ch.default_exchange.publish("", :routing_key => queue_name) }.to raise_error(Bunny::ConnectionClosedError)
29
+ end
30
+ ensure
31
+ cleanup_toxiproxy
32
+ end
33
+ end
34
+ end
35
+
36
+ context "when the the connection detects missed heartbeats without automatic recovery" do
37
+ before(:each) do
38
+ setup_toxiproxy
39
+ @connection = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed",
40
+ host: "localhost:11111", heartbeat_timeout: 1, automatically_recover: false, threaded: false)
41
+ @connection.start
42
+ end
43
+
44
+ it "does not raise an exception on session thread" do
45
+ rabbitmq_toxiproxy.down do
46
+ sleep 5
47
+ end
48
+ end
49
+ end
50
+
51
+ context "recovery attempt limit that's exceeded" do
52
+ before(:each) do
53
+ setup_toxiproxy
54
+ @connection = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed",
55
+ host: "localhost:11111", heartbeat_timeout: 1, automatically_recover: true, network_recovery_interval: 1,
56
+ recovery_attempts: 3, reset_recovery_attempts_after_reconnection: true)
57
+ @connection.start
58
+ end
59
+
60
+ it "permanently closes connection" do
61
+ expect(@connection.open?).to be(true)
62
+
63
+ rabbitmq_toxiproxy.down do
64
+ sleep 6
65
+ end
66
+ # give the connection oen last chance to recover
67
+ sleep 3
68
+
69
+ expect(@connection.closed?).to be(true)
70
+ end
71
+ end # context
72
+ end # describe
73
+ else
74
+ puts "Toxiproxy isn't running, some examples will be skipped"
75
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bunny::Session do
4
+ context 'when retry attempts have been exhausted' do
5
+ let(:io) { StringIO.new } # keep test output clear
6
+
7
+ def create_session
8
+ described_class.new(
9
+ host: 'fake.host',
10
+ recovery_attempts: 0,
11
+ connection_timeout: 0,
12
+ network_recovery_interval: 0,
13
+ logfile: io,
14
+ )
15
+ end
16
+
17
+ it 'closes the session' do
18
+ session = create_session
19
+ session.handle_network_failure(StandardError.new)
20
+ expect(session.closed?).to be true
21
+ end
22
+
23
+ it 'stops the reader loop' do
24
+ session = create_session
25
+ reader_loop = session.reader_loop
26
+ session.handle_network_failure(StandardError.new)
27
+ expect(reader_loop.stopping?).to be true
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module RabbitMQ
2
+ module Toxiproxy
3
+ RABBITMQ_UPSTREAM_HOST = if !ENV["LOCAL_RABBITMQ"].nil?
4
+ # a local Toxiproxy/RabbitMQ combination
5
+ "localhost"
6
+ else
7
+ # docker-compose
8
+ "rabbitmq"
9
+ end
10
+
11
+ def setup_toxiproxy
12
+ ::Toxiproxy.populate([{
13
+ name: "rabbitmq",
14
+ listen: "0.0.0.0:11111",
15
+ upstream: "#{RABBITMQ_UPSTREAM_HOST}:5672"
16
+ }])
17
+ rabbitmq_toxiproxy.enable
18
+ end
19
+
20
+ def cleanup_toxiproxy
21
+ ::Toxiproxy.populate()
22
+ end
23
+
24
+ def rabbitmq_toxiproxy
25
+ ::Toxiproxy[/rabbitmq/]
26
+ end
27
+ end
28
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0
4
+ version: 2.12.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -12,13 +12,16 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2018-06-21 00:00:00.000000000 Z
15
+ date: 2018-09-08 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol
19
19
  requirement: !ruby/object:Gem::Requirement
20
20
  requirements:
21
21
  - - "~>"
22
+ - !ruby/object:Gem::Version
23
+ version: '2.3'
24
+ - - ">="
22
25
  - !ruby/object:Gem::Version
23
26
  version: 2.3.0
24
27
  type: :runtime
@@ -26,6 +29,9 @@ dependencies:
26
29
  version_requirements: !ruby/object:Gem::Requirement
27
30
  requirements:
28
31
  - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.3'
34
+ - - ">="
29
35
  - !ruby/object:Gem::Version
30
36
  version: 2.3.0
31
37
  description: Easy to use, feature complete Ruby client for RabbitMQ 3.3 and later
@@ -174,6 +180,7 @@ files:
174
180
  - spec/higher_level_api/integration/read_only_consumer_spec.rb
175
181
  - spec/higher_level_api/integration/sender_selected_distribution_spec.rb
176
182
  - spec/higher_level_api/integration/tls_connection_spec.rb
183
+ - spec/higher_level_api/integration/toxiproxy_spec.rb
177
184
  - spec/higher_level_api/integration/tx_commit_spec.rb
178
185
  - spec/higher_level_api/integration/tx_rollback_spec.rb
179
186
  - spec/higher_level_api/integration/with_channel_spec.rb
@@ -182,6 +189,7 @@ files:
182
189
  - spec/issues/issue202_spec.rb
183
190
  - spec/issues/issue224_spec.rb
184
191
  - spec/issues/issue465_spec.rb
192
+ - spec/issues/issue549_spec.rb
185
193
  - spec/issues/issue78_spec.rb
186
194
  - spec/issues/issue83_spec.rb
187
195
  - spec/issues/issue97_attachment.json
@@ -205,6 +213,7 @@ files:
205
213
  - spec/tls/server.csr
206
214
  - spec/tls/server_certificate.pem
207
215
  - spec/tls/server_key.pem
216
+ - spec/toxiproxy_helper.rb
208
217
  - spec/unit/bunny_spec.rb
209
218
  - spec/unit/concurrent/atomic_fixnum_spec.rb
210
219
  - spec/unit/concurrent/condition_spec.rb
@@ -227,12 +236,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
227
236
  version: '2.2'
228
237
  required_rubygems_version: !ruby/object:Gem::Requirement
229
238
  requirements:
230
- - - ">="
239
+ - - ">"
231
240
  - !ruby/object:Gem::Version
232
- version: '0'
241
+ version: 1.3.1
233
242
  requirements: []
234
243
  rubyforge_project:
235
- rubygems_version: 2.6.11
244
+ rubygems_version: 2.7.7
236
245
  signing_key:
237
246
  specification_version: 4
238
247
  summary: Popular easy to use Ruby client for RabbitMQ
@@ -275,6 +284,7 @@ test_files:
275
284
  - spec/higher_level_api/integration/read_only_consumer_spec.rb
276
285
  - spec/higher_level_api/integration/sender_selected_distribution_spec.rb
277
286
  - spec/higher_level_api/integration/tls_connection_spec.rb
287
+ - spec/higher_level_api/integration/toxiproxy_spec.rb
278
288
  - spec/higher_level_api/integration/tx_commit_spec.rb
279
289
  - spec/higher_level_api/integration/tx_rollback_spec.rb
280
290
  - spec/higher_level_api/integration/with_channel_spec.rb
@@ -283,6 +293,7 @@ test_files:
283
293
  - spec/issues/issue202_spec.rb
284
294
  - spec/issues/issue224_spec.rb
285
295
  - spec/issues/issue465_spec.rb
296
+ - spec/issues/issue549_spec.rb
286
297
  - spec/issues/issue78_spec.rb
287
298
  - spec/issues/issue83_spec.rb
288
299
  - spec/issues/issue97_attachment.json
@@ -306,6 +317,7 @@ test_files:
306
317
  - spec/tls/server.csr
307
318
  - spec/tls/server_certificate.pem
308
319
  - spec/tls/server_key.pem
320
+ - spec/toxiproxy_helper.rb
309
321
  - spec/unit/bunny_spec.rb
310
322
  - spec/unit/concurrent/atomic_fixnum_spec.rb
311
323
  - spec/unit/concurrent/condition_spec.rb