bunny 1.5.1 → 1.6.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
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