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 +5 -5
- data/CONTRIBUTING.md +1 -1
- data/ChangeLog.md +62 -1
- data/Gemfile +9 -6
- data/README.md +3 -2
- data/bunny.gemspec +1 -2
- data/docker-compose.yml +9 -1
- data/docker/Dockerfile +6 -6
- data/lib/bunny/channel.rb +77 -39
- data/lib/bunny/consumer_work_pool.rb +1 -0
- data/lib/bunny/cruby/ssl_socket.rb +5 -0
- data/lib/bunny/heartbeat_sender.rb +2 -1
- data/lib/bunny/jruby/ssl_socket.rb +5 -0
- data/lib/bunny/queue.rb +0 -5
- data/lib/bunny/reader_loop.rb +4 -0
- data/lib/bunny/session.rb +22 -14
- data/lib/bunny/transport.rb +4 -2
- data/lib/bunny/version.rb +1 -1
- data/repl +1 -1
- data/spec/higher_level_api/integration/toxiproxy_spec.rb +75 -0
- data/spec/issues/issue549_spec.rb +30 -0
- data/spec/toxiproxy_helper.rb +28 -0
- metadata +17 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 80a243c18a410ace3d56439bf7cbb60141112e388db773e7b3fe1bbe6594ba30
|
4
|
+
data.tar.gz: 548aa57383b638a79a2b4863255c8235304b498a850204977ed965118d9131c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc92d15fa248ed121180d78db848b4b8399104e4d72aea6cbadca4a14ece080cd893a6db75b8c29fc02c002510ad15a0949492ce19062d8bb4b78e90d84500f9
|
7
|
+
data.tar.gz: 7cc116eec7ae1119d8216853af7f266e202756b983b910ae56c129ab0b2dbd93bb55213c2e682e56c3e56c5aac98ba2e04e92b57873702c51e358f0975112929
|
data/CONTRIBUTING.md
CHANGED
@@ -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
|
data/ChangeLog.md
CHANGED
@@ -1,4 +1,64 @@
|
|
1
|
-
## Changes between Bunny 2.
|
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(:
|
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", ">=
|
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", :
|
29
|
-
gem "ruby-prof", :
|
28
|
+
gem "redcarpet", platform: :mri
|
29
|
+
gem "ruby-prof", platform: :mri
|
30
30
|
|
31
|
-
gem "
|
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(:
|
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.
|
99
|
+
gem "bunny", ">= 2.11.0"
|
99
100
|
```
|
100
101
|
|
101
102
|
|
data/bunny.gemspec
CHANGED
@@ -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.
|
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")
|
data/docker-compose.yml
CHANGED
data/docker/Dockerfile
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
FROM
|
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
|
7
|
-
wget -O- https://
|
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
|
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
|
data/lib/bunny/channel.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
1016
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
1157
|
+
# Declares a exchange using echange.declare AMQP 0.9.1 method.
|
1154
1158
|
#
|
1155
|
-
# @param [String] 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
|
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
|
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/
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|
data/lib/bunny/queue.rb
CHANGED
@@ -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
|
data/lib/bunny/reader_loop.rb
CHANGED
data/lib/bunny/session.rb
CHANGED
@@ -78,7 +78,7 @@ module Bunny
|
|
78
78
|
|
79
79
|
# @return [Bunny::Transport]
|
80
80
|
attr_reader :transport
|
81
|
-
attr_reader :status, :
|
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
|
-
|
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
|
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
|
-
|
1082
|
-
|
1083
|
-
|
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
|
-
|
1100
|
-
|
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
|
|
data/lib/bunny/transport.rb
CHANGED
@@ -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)
|
data/lib/bunny/version.rb
CHANGED
data/repl
CHANGED
@@ -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.
|
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-
|
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:
|
241
|
+
version: 1.3.1
|
233
242
|
requirements: []
|
234
243
|
rubyforge_project:
|
235
|
-
rubygems_version: 2.
|
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
|