bunny 2.11.0 → 2.12.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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