bunny 1.5.1 → 1.6.0.pre1

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
2
  SHA1:
3
- metadata.gz: 53c74a39ad4bc726f41f1c2db1f6ad7e7ebdefb8
4
- data.tar.gz: 4ede7c5896c820ab5895963f1da741dda42e9fe7
3
+ metadata.gz: de1627dd44089041db13ab6a5ea76ba2ead5086d
4
+ data.tar.gz: 41ae99e1499f57a77636392e2a5d870c6c698ef7
5
5
  SHA512:
6
- metadata.gz: 14e4fbe2d2aca163498882cb49ee7d6ec90756a7c0c6e419b5c73e3afc31673451e4b75a4f47f1c49e5525dd91b5b792d074fa7edef18aa18957a542896bb02c
7
- data.tar.gz: d750d4af8cf6fd512a60422865b4e8df3e0dcd6c6f72eb95ed671bd73ba053e3fca8dea7d853517502de59f0f43e0b3e7531f13f7a23d622050e98aaea2b552f
6
+ metadata.gz: 1e24bb89154db20fe6badb7427bab93de1d9a2e4c880b972340b5d76f4d465aa6410c75c55f5a51a091526deec93d30ced4cd815080d7dc0a7dbce06eb89d95b
7
+ data.tar.gz: 4d210c02531f031fdfc5ab021d02b32abdc2d4019440bf76650743e950f310cf2ec27e09193feee987cfc97f65c6d026a1cd0451a4367f35f84c8ce003fe7318
@@ -1,3 +1,25 @@
1
+ ## Changes between Bunny 1.5.0 and 1.6.0
2
+
3
+ ### Socket Read and Write Timeout Improvements
4
+
5
+ Bunny now sets a read timeout on the sockets it opens, and uses
6
+ `IO.select` timeouts as the most reliable option available
7
+ on Ruby 1.9 and later.
8
+
9
+ GH issue: [#254](https://github.com/ruby-amqp/bunny/pull/254).
10
+
11
+ Contributed by Andre Foeken (Nedap).
12
+
13
+ ### Inline TLS Certificates Support
14
+
15
+ TLS certificate options now accept inline certificates as well as
16
+ file paths.
17
+
18
+ GH issues: [#255](https://github.com/ruby-amqp/bunny/pull/255), [#256](https://github.com/ruby-amqp/bunny/pull/256).
19
+
20
+ Contributed by Will Barrett (Sqwiggle).
21
+
22
+
1
23
  ## Changes between Bunny 1.4.0 and 1.5.0
2
24
 
3
25
  ### Improved Uncaught Exception Handler
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.version = Bunny::VERSION.dup
10
10
  s.homepage = "http://rubybunny.info"
11
11
  s.summary = "Popular easy to use Ruby client for RabbitMQ"
12
- s.description = "Easy to use, feature complete Ruby client for RabbitMQ 2.0 and later versions."
12
+ s.description = "Easy to use, feature complete Ruby client for RabbitMQ 3.3 and later versions."
13
13
  s.license = "MIT"
14
14
 
15
15
  # Sorted alphabetically.
@@ -203,8 +203,8 @@ module Bunny
203
203
  attr_reader :recoveries_counter
204
204
 
205
205
  # @private
206
- def read_write_timeout
207
- @connection.read_write_timeout
206
+ def wait_on_continuations_timeout
207
+ @connection.transport_write_timeout
208
208
  end
209
209
 
210
210
  # Opens the channel and resets its internal state
@@ -629,7 +629,7 @@ module Bunny
629
629
 
630
630
  @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
631
631
 
632
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
632
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
633
633
  @last_basic_qos_ok = wait_on_continuations
634
634
  end
635
635
  raise_if_continuation_resulted_in_a_channel_error!
@@ -648,7 +648,7 @@ module Bunny
648
648
  raise_if_no_longer_open!
649
649
 
650
650
  @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
651
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
651
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
652
652
  @last_basic_recover_ok = wait_on_continuations
653
653
  end
654
654
  raise_if_continuation_resulted_in_a_channel_error!
@@ -850,7 +850,7 @@ module Bunny
850
850
  arguments))
851
851
 
852
852
  begin
853
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
853
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
854
854
  @last_basic_consume_ok = wait_on_continuations
855
855
  end
856
856
  rescue Exception => e
@@ -900,7 +900,7 @@ module Bunny
900
900
  consumer.arguments))
901
901
 
902
902
  begin
903
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
903
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
904
904
  @last_basic_consume_ok = wait_on_continuations
905
905
  end
906
906
  rescue Exception => e
@@ -935,7 +935,7 @@ module Bunny
935
935
  def basic_cancel(consumer_tag)
936
936
  @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
937
937
 
938
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
938
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
939
939
  @last_basic_cancel_ok = wait_on_continuations
940
940
  end
941
941
 
@@ -1009,7 +1009,7 @@ module Bunny
1009
1009
  opts[:if_unused],
1010
1010
  opts[:if_empty],
1011
1011
  false))
1012
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1012
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1013
1013
  @last_queue_delete_ok = wait_on_continuations
1014
1014
  end
1015
1015
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1029,7 +1029,7 @@ module Bunny
1029
1029
 
1030
1030
  @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
1031
1031
 
1032
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1032
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1033
1033
  @last_queue_purge_ok = wait_on_continuations
1034
1034
  end
1035
1035
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1065,7 +1065,7 @@ module Bunny
1065
1065
  (opts[:routing_key] || opts[:key]),
1066
1066
  false,
1067
1067
  opts[:arguments]))
1068
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1068
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1069
1069
  @last_queue_bind_ok = wait_on_continuations
1070
1070
  end
1071
1071
 
@@ -1100,7 +1100,7 @@ module Bunny
1100
1100
  exchange_name,
1101
1101
  opts[:routing_key],
1102
1102
  opts[:arguments]))
1103
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1103
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1104
1104
  @last_queue_unbind_ok = wait_on_continuations
1105
1105
  end
1106
1106
 
@@ -1139,7 +1139,7 @@ module Bunny
1139
1139
  opts.fetch(:internal, false),
1140
1140
  false, # nowait
1141
1141
  opts[:arguments]))
1142
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1142
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1143
1143
  @last_exchange_declare_ok = wait_on_continuations
1144
1144
  end
1145
1145
 
@@ -1164,7 +1164,7 @@ module Bunny
1164
1164
  name,
1165
1165
  opts[:if_unused],
1166
1166
  false))
1167
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1167
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1168
1168
  @last_exchange_delete_ok = wait_on_continuations
1169
1169
  end
1170
1170
 
@@ -1208,7 +1208,7 @@ module Bunny
1208
1208
  opts[:routing_key],
1209
1209
  false,
1210
1210
  opts[:arguments]))
1211
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1211
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1212
1212
  @last_exchange_bind_ok = wait_on_continuations
1213
1213
  end
1214
1214
 
@@ -1252,7 +1252,7 @@ module Bunny
1252
1252
  opts[:routing_key],
1253
1253
  false,
1254
1254
  opts[:arguments]))
1255
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1255
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1256
1256
  @last_exchange_unbind_ok = wait_on_continuations
1257
1257
  end
1258
1258
 
@@ -1280,7 +1280,7 @@ module Bunny
1280
1280
  raise_if_no_longer_open!
1281
1281
 
1282
1282
  @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
1283
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1283
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1284
1284
  @last_channel_flow_ok = wait_on_continuations
1285
1285
  end
1286
1286
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1301,7 +1301,7 @@ module Bunny
1301
1301
  raise_if_no_longer_open!
1302
1302
 
1303
1303
  @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
1304
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1304
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1305
1305
  @last_tx_select_ok = wait_on_continuations
1306
1306
  end
1307
1307
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1317,7 +1317,7 @@ module Bunny
1317
1317
  raise_if_no_longer_open!
1318
1318
 
1319
1319
  @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
1320
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1320
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1321
1321
  @last_tx_commit_ok = wait_on_continuations
1322
1322
  end
1323
1323
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1332,7 +1332,7 @@ module Bunny
1332
1332
  raise_if_no_longer_open!
1333
1333
 
1334
1334
  @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
1335
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1335
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1336
1336
  @last_tx_rollback_ok = wait_on_continuations
1337
1337
  end
1338
1338
  raise_if_continuation_resulted_in_a_channel_error!
@@ -1379,7 +1379,7 @@ module Bunny
1379
1379
  @confirms_callback = callback
1380
1380
 
1381
1381
  @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
1382
- Bunny::Timeout.timeout(read_write_timeout, ClientTimeout) do
1382
+ Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
1383
1383
  @last_confirm_select_ok = wait_on_continuations
1384
1384
  end
1385
1385
  @confirm_mode = true
@@ -13,8 +13,12 @@ module Bunny
13
13
  READ_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
14
14
  READ_RETRY_EXCEPTION_CLASSES << IO::WaitReadable if IO.const_defined?(:WaitReadable)
15
15
 
16
+ # IO::WaitWritable is 1.9+ only
17
+ WRITE_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
18
+ WRITE_RETRY_EXCEPTION_CLASSES << IO::WaitWritable if IO.const_defined?(:WaitWritable)
19
+
16
20
  def self.open(host, port, options = {})
17
- Timeout.timeout(options[:socket_timeout], ClientTimeout) do
21
+ Timeout.timeout(options[:connect_timeout], ClientTimeout) do
18
22
  sock = new(host, port)
19
23
  if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
20
24
  sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
@@ -62,22 +66,32 @@ module Bunny
62
66
  # if this is not appropriate in your case.
63
67
  #
64
68
  # @param [String] data Data to write
69
+ # @param [Integer] timeout Timeout
65
70
  #
66
71
  # @api public
67
- def write_nonblock_fully(data)
72
+ def write_nonblock_fully(data, timeout = nil)
68
73
  return nil if @__bunny_socket_eof_flag__
69
74
 
70
- begin
71
- while !data.empty?
72
- written = self.write_nonblock(data)
73
- data.slice!(0, written)
75
+ length = data.bytesize
76
+ total_count = 0
77
+ count = 0
78
+ loop do
79
+ begin
80
+ count = self.write_nonblock(data)
81
+ rescue *WRITE_RETRY_EXCEPTION_CLASSES
82
+ if IO.select([], [self], nil, timeout)
83
+ retry
84
+ else
85
+ raise Timeout::Error, "IO timeout when writing to socket"
86
+ end
74
87
  end
75
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN
76
- IO.select([], [self])
77
- retry
88
+
89
+ total_count += count
90
+ return total_count if total_count >= length
91
+ data = data.byteslice(count..-1)
78
92
  end
79
93
 
80
- data.bytesize
81
94
  end
95
+
82
96
  end
83
97
  end
@@ -12,6 +12,9 @@ module Bunny
12
12
  READ_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
13
13
  READ_RETRY_EXCEPTION_CLASSES << IO::WaitReadable if IO.const_defined?(:WaitReadable)
14
14
 
15
+ # IO::WaitWritable is 1.9+ only
16
+ WRITE_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
17
+ WRITE_RETRY_EXCEPTION_CLASSES << IO::WaitWritable if IO.const_defined?(:WaitWritable)
15
18
 
16
19
  # Reads given number of bytes with an optional timeout
17
20
  #
@@ -50,6 +53,51 @@ module Bunny
50
53
  end
51
54
  value
52
55
  end
56
+
57
+ # Writes provided data using IO#write_nonblock, taking care of handling
58
+ # of exceptions it raises when writing would fail (e.g. due to socket buffer
59
+ # being full).
60
+ #
61
+ # IMPORTANT: this method will mutate (slice) the argument. Pass in duplicates
62
+ # if this is not appropriate in your case.
63
+ #
64
+ # @param [String] data Data to write
65
+ # @param [Integer] timeout Timeout
66
+ #
67
+ # @api public
68
+ def write_nonblock_fully(data, timeout = nil)
69
+ return nil if @__bunny_socket_eof_flag__
70
+
71
+ length = data.bytesize
72
+ total_count = 0
73
+ count = 0
74
+ loop do
75
+ begin
76
+ count = self.write_nonblock(data)
77
+ rescue OpenSSL::SSL::SSLError => e
78
+ if e.message == "write would block"
79
+ if IO.select([], [self], nil, timeout)
80
+ retry
81
+ else
82
+ raise Timeout::Error, "IO timeout when writing to socket"
83
+ end
84
+ end
85
+ raise e
86
+ rescue *WRITE_RETRY_EXCEPTION_CLASSES
87
+ if IO.select([], [self], nil, timeout)
88
+ retry
89
+ else
90
+ raise Timeout::Error, "IO timeout when writing to socket"
91
+ end
92
+ end
93
+
94
+ total_count += count
95
+ return total_count if total_count >= length
96
+ data = data.byteslice(count..-1)
97
+ end
98
+
99
+ end
100
+
53
101
  end
54
102
  rescue LoadError => le
55
103
  puts "Could not load OpenSSL"
@@ -154,7 +154,7 @@ module Bunny
154
154
  @network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
155
155
  @recover_from_connection_close = opts.fetch(:recover_from_connection_close, false)
156
156
  # in ms
157
- @continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT)
157
+ @continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT)
158
158
 
159
159
  @status = :not_connected
160
160
  @blocked = false
@@ -279,9 +279,11 @@ module Bunny
279
279
  self.start_reader_loop if threaded?
280
280
 
281
281
  rescue TCPConnectionFailed => e
282
- self.initialize_transport
283
282
 
284
283
  @logger.warn e.message
284
+
285
+ self.initialize_transport
286
+
285
287
  @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
286
288
 
287
289
  return self.start
@@ -299,11 +301,11 @@ module Bunny
299
301
  self
300
302
  end
301
303
 
302
- # Socket operation timeout used by this connection
304
+ # Socket operation write timeout used by this connection
303
305
  # @return [Integer]
304
306
  # @private
305
- def read_write_timeout
306
- @transport.read_write_timeout
307
+ def transport_write_timeout
308
+ @transport.write_timeout
307
309
  end
308
310
 
309
311
  # Opens a new channel and returns it. This method will block the calling
@@ -645,8 +647,9 @@ module Bunny
645
647
  sleep @network_recovery_interval
646
648
  @logger.debug "About to start connection recovery..."
647
649
 
648
- self.reset_host_index # since we are starting a fresh try.
649
650
  self.initialize_transport
651
+
652
+ @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
650
653
  self.start
651
654
 
652
655
  if open?
@@ -654,6 +657,9 @@ module Bunny
654
657
 
655
658
  recover_channels
656
659
  end
660
+ rescue HostListDepleted
661
+ reset_host_index
662
+ retry
657
663
  rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e
658
664
  @logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds"
659
665
  sleep @network_recovery_interval
@@ -990,6 +996,10 @@ module Bunny
990
996
  @logger.debug "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}"
991
997
  @logger.info "Heartbeat interval used (in seconds): #{@heartbeat}"
992
998
 
999
+ # We set the read_write_timeout to twice the heartbeat value
1000
+ # This allows us to miss a single heartbeat before we time out the socket.
1001
+ @transport.read_timeout = @heartbeat * 2
1002
+
993
1003
  # if there are existing channels we've just recovered from
994
1004
  # a network failure and need to fix the allocated set. See issue 205. MK.
995
1005
  if @channels.empty?
@@ -1079,7 +1089,13 @@ module Bunny
1079
1089
  def initialize_transport
1080
1090
  if host = @hosts[ @host_index ]
1081
1091
  @host_index_mutex.synchronize { @host_index += 1 }
1092
+ @transport.close rescue nil # Let's make sure the previous transport socket is closed
1082
1093
  @transport = Transport.new(self, host, @port, @opts.merge(:session_thread => @origin_thread))
1094
+
1095
+ # Reset the cached progname for the logger
1096
+ @logger.progname = to_s if @logger.respond_to?(:progname)
1097
+
1098
+ @transport
1083
1099
  else
1084
1100
  raise HostListDepleted
1085
1101
  end
@@ -21,12 +21,19 @@ module Bunny
21
21
 
22
22
  # Default TCP connection timeout
23
23
  DEFAULT_CONNECTION_TIMEOUT = 5.0
24
+
24
25
  DEFAULT_READ_TIMEOUT = 5.0
25
26
  DEFAULT_WRITE_TIMEOUT = 5.0
26
27
 
27
- attr_reader :session, :host, :port, :socket, :connect_timeout, :read_write_timeout, :disconnect_timeout
28
+ # Default TLS protocol version to use.
29
+ # Currently SSLv3, same as in RabbitMQ Java client
30
+ DEFAULT_TLS_PROTOCOL = "SSLv3"
31
+
32
+ attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
28
33
  attr_reader :tls_context
29
34
 
35
+ attr_writer :read_timeout
36
+
30
37
  def initialize(session, host, port, opts)
31
38
  @session = session
32
39
  @session_thread = opts[:session_thread]
@@ -37,11 +44,17 @@ module Bunny
37
44
  @logger = session.logger
38
45
  @tls_enabled = tls_enabled?(opts)
39
46
 
40
- @read_write_timeout = opts[:socket_timeout] || 3
41
- @read_write_timeout = nil if @read_write_timeout == 0
47
+ @read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
48
+ @read_timeout = nil if @read_timeout == 0
49
+
50
+ @write_timeout = opts[:socket_timeout] # Backwards compatability
51
+
52
+ @write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
53
+ @write_timeout = nil if @write_timeout == 0
54
+
42
55
  @connect_timeout = self.timeout_from(opts)
43
56
  @connect_timeout = nil if @connect_timeout == 0
44
- @disconnect_timeout = @read_write_timeout || @connect_timeout
57
+ @disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
45
58
 
46
59
  @writes_mutex = @session.mutex_impl.new
47
60
 
@@ -92,26 +105,20 @@ module Bunny
92
105
  block.call(@tls_context) if @tls_context
93
106
  end
94
107
 
95
- # Writes data to the socket. If read/write timeout was specified, Bunny::ClientTimeout will be raised
96
- # if the operation times out.
108
+ # Writes data to the socket. If read/write timeout was specified the operation will return after that
109
+ # amount of time has elapsed waiting for the socket.
97
110
  #
98
111
  # @raise [ClientTimeout]
99
112
  def write(data)
113
+ return write_without_timeout(data) unless @write_timeout
114
+
100
115
  begin
101
- if @read_write_timeout
102
- Bunny::Timeout.timeout(@read_write_timeout, Bunny::ClientTimeout) do
103
- if open?
104
- @writes_mutex.synchronize { @socket.write(data) }
105
- @socket.flush
106
- end
107
- end
108
- else
109
- if open?
110
- @writes_mutex.synchronize { @socket.write(data) }
111
- @socket.flush
116
+ if open?
117
+ @writes_mutex.synchronize do
118
+ @socket.write_nonblock_fully(data, @write_timeout)
112
119
  end
113
120
  end
114
- rescue SystemCallError, Bunny::ClientTimeout, Bunny::ConnectionError, IOError => e
121
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
115
122
  @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
116
123
  close
117
124
  @status = :not_connected
@@ -181,8 +188,20 @@ module Bunny
181
188
  @socket.flush if @socket
182
189
  end
183
190
 
184
- def read_fully(*args)
185
- @socket.read_fully(*args)
191
+ def read_fully(count)
192
+ begin
193
+ @socket.read_fully(count, @read_timeout)
194
+ rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
195
+ @logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
196
+ close
197
+ @status = :not_connected
198
+
199
+ if @session.automatically_recover?
200
+ @session.handle_network_failure(e)
201
+ else
202
+ @session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
203
+ end
204
+ end
186
205
  end
187
206
 
188
207
  def read_ready?(timeout = nil)
@@ -190,11 +209,10 @@ module Bunny
190
209
  io && io[0].include?(@socket)
191
210
  end
192
211
 
193
-
194
212
  # Exposed primarily for Bunny::Channel
195
213
  # @private
196
214
  def read_next_frame(opts = {})
197
- header = @socket.read_fully(7)
215
+ header = read_fully(7)
198
216
  # TODO: network issues here will sometimes cause
199
217
  # the socket method return an empty string. We need to log
200
218
  # and handle this better.
@@ -204,8 +222,8 @@ module Bunny
204
222
  # puts "Got AMQ::Protocol::EmptyResponseError, header is #{header.inspect}"
205
223
  # end
206
224
  type, channel, size = AMQ::Protocol::Frame.decode_header(header)
207
- payload = @socket.read_fully(size)
208
- frame_end = @socket.read_fully(1)
225
+ payload = read_fully(size)
226
+ frame_end = read_fully(1)
209
227
 
210
228
  # 1) the size is miscalculated
211
229
  if payload.bytesize != size
@@ -221,7 +239,7 @@ module Bunny
221
239
  def self.reacheable?(host, port, timeout)
222
240
  begin
223
241
  s = Bunny::SocketImpl.open(host, port,
224
- :socket_timeout => timeout)
242
+ :connect_timeout => timeout)
225
243
 
226
244
  true
227
245
  rescue SocketError, Timeout::Error => e
@@ -239,7 +257,7 @@ module Bunny
239
257
  begin
240
258
  @socket = Bunny::SocketImpl.open(@host, @port,
241
259
  :keepalive => @opts[:keepalive],
242
- :socket_timeout => @connect_timeout)
260
+ :connect_timeout => @connect_timeout)
243
261
  rescue StandardError, ClientTimeout => e
244
262
  @status = :not_connected
245
263
  raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
@@ -294,7 +312,7 @@ module Bunny
294
312
 
295
313
 
296
314
  def inline_client_certificate_from(opts)
297
- opts[:tls_certificate] || opts[:ssl_cert_string]
315
+ opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
298
316
  end
299
317
 
300
318
  def inline_client_key_from(opts)
@@ -313,7 +331,7 @@ module Bunny
313
331
  @tls_ca_certificates = opts.fetch(:tls_ca_certificates, default_tls_certificates)
314
332
  @verify_peer = opts[:verify_ssl] || opts[:verify_peer]
315
333
 
316
- @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts)
334
+ @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new)
317
335
  end
318
336
 
319
337
  def wrap_in_tls_socket(socket)
@@ -347,7 +365,7 @@ module Bunny
347
365
  end
348
366
  end
349
367
 
350
- def initialize_tls_context(ctx, opts={})
368
+ def initialize_tls_context(ctx)
351
369
  ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate
352
370
  ctx.key = OpenSSL::PKey::RSA.new(@tls_key) if @tls_key
353
371
  ctx.cert_store = if @tls_certificate_store
@@ -366,15 +384,17 @@ module Bunny
366
384
  @logger.warn "Using TLS but no client private key is provided!"
367
385
  end
368
386
 
387
+ # setting TLS/SSL version only works correctly when done
388
+ # vis set_params. MK.
389
+ ctx.set_params(:ssl_version => @opts.fetch(:tls_protocol, DEFAULT_TLS_PROTOCOL))
390
+
369
391
  verify_mode = if @verify_peer
370
392
  OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
371
393
  else
372
394
  OpenSSL::SSL::VERIFY_NONE
373
395
  end
374
- ctx.verify_mode = verify_mode
375
396
 
376
- ssl_version = opts[:tls_protocol] || opts[:ssl_version]
377
- ctx.ssl_version = ssl_version if ssl_version
397
+ ctx.set_params(:verify_mode => verify_mode)
378
398
 
379
399
  ctx
380
400
  end
@@ -397,13 +417,23 @@ module Bunny
397
417
  end
398
418
 
399
419
  def initialize_tls_certificate_store(certs)
400
- certs = certs.select { |path| File.readable? path }
401
- @logger.debug "Using CA certificates at #{certs.join(', ')}"
420
+ cert_files = []
421
+ cert_inlines = []
422
+ certs.each do |cert|
423
+ if File.readable? cert
424
+ cert_files.push(cert)
425
+ else
426
+ cert_inlines.push(cert)
427
+ end
428
+ end
429
+ @logger.debug "Using CA certificates at #{cert_files.join(', ')}"
430
+ @logger.debug "Using #{cert_inlines.count} inline ca_certificates"
402
431
  if certs.empty?
403
432
  @logger.error "No CA certificates found, add one with :tls_ca_certificates"
404
433
  end
405
434
  OpenSSL::X509::Store.new.tap do |store|
406
- certs.each { |path| store.add_file(path) }
435
+ cert_files.each { |path| store.add_file(path) }
436
+ cert_inlines.each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) }
407
437
  end
408
438
  end
409
439
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bunny
4
4
  # @return [String] Version of the library
5
- VERSION = "1.5.1"
5
+ VERSION = "1.6.0.pre1"
6
6
  end
@@ -124,51 +124,4 @@ unless ENV["CI"]
124
124
 
125
125
  include_examples "successful TLS connection"
126
126
  end
127
-
128
-
129
- describe "TLS connection to RabbitMQ with ssl_version SSLv3 specified" do
130
- let(:connection) do
131
- c = Bunny.new(:user => "bunny_gem",
132
- :password => "bunny_password",
133
- :vhost => "bunny_testbed",
134
- :tls => true,
135
- :ssl_version => :SSLv3,
136
- :tls_ca_certificates => ["./spec/tls/cacert.pem"])
137
- c.start
138
- c
139
- end
140
-
141
- after :each do
142
- connection.close
143
- end
144
-
145
- include_examples "successful TLS connection"
146
-
147
- it "connects using SSLv3" do
148
- connection.transport.socket.ssl_version.should == "SSLv3"
149
- end
150
- end
151
-
152
- describe "TLS connection to RabbitMQ with tls_version TLSv1 specified" do
153
- let(:connection) do
154
- c = Bunny.new(:user => "bunny_gem",
155
- :password => "bunny_password",
156
- :vhost => "bunny_testbed",
157
- :tls => true,
158
- :tls_protocol => :TLSv1,
159
- :tls_ca_certificates => ["./spec/tls/cacert.pem"])
160
- c.start
161
- c
162
- end
163
-
164
- after :each do
165
- connection.close
166
- end
167
-
168
- include_examples "successful TLS connection"
169
-
170
- it "connects using TLSv1" do
171
- connection.transport.socket.ssl_version.should == "TLSv1"
172
- end
173
- end
174
127
  end
@@ -6,7 +6,8 @@ unless ENV["CI"]
6
6
  c = Bunny.new(:user => "bunny_gem",
7
7
  :password => "bunny_password",
8
8
  :vhost => "bunny_testbed",
9
- :socket_timeout => 0)
9
+ :write_timeout => 0,
10
+ :read_timeout => 0)
10
11
  c.start
11
12
  c
12
13
  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: 1.5.1
4
+ version: 1.6.0.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Duncan
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2014-10-22 00:00:00.000000000 Z
15
+ date: 2014-10-21 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: amq-protocol
@@ -28,7 +28,7 @@ dependencies:
28
28
  - - '>='
29
29
  - !ruby/object:Gem::Version
30
30
  version: 1.9.2
31
- description: Easy to use, feature complete Ruby client for RabbitMQ 2.0 and later
31
+ description: Easy to use, feature complete Ruby client for RabbitMQ 3.3 and later
32
32
  versions.
33
33
  email:
34
34
  - celldee@gmail.com
@@ -225,9 +225,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
225
225
  version: '0'
226
226
  required_rubygems_version: !ruby/object:Gem::Requirement
227
227
  requirements:
228
- - - '>='
228
+ - - '>'
229
229
  - !ruby/object:Gem::Version
230
- version: '0'
230
+ version: 1.3.1
231
231
  requirements: []
232
232
  rubyforge_project:
233
233
  rubygems_version: 2.2.2