bunny 2.24.0 → 3.0.0

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.
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Bunny
4
4
  # Wraps AMQ::Protocol::Basic::Return to
5
- # provide access to the delivery properties as immutable hash as
6
- # well as methods.
5
+ # provide access to the return properties as immutable hash as
6
+ # well as methods. Hash representation is created lazily.
7
7
  class ReturnInfo
8
8
 
9
9
  #
@@ -18,29 +18,34 @@ module Bunny
18
18
 
19
19
  def initialize(basic_return)
20
20
  @basic_return = basic_return
21
- @hash = {
22
- :reply_code => basic_return.reply_code,
23
- :reply_text => basic_return.reply_text,
24
- :exchange => basic_return.exchange,
25
- :routing_key => basic_return.routing_key
26
- }
27
21
  end
28
22
 
29
23
  # Iterates over the returned delivery properties
30
24
  # @see Enumerable#each
31
25
  def each(*args, &block)
32
- @hash.each(*args, &block)
26
+ to_hash.each(*args, &block)
33
27
  end
34
28
 
35
29
  # Accesses returned delivery properties by key
36
30
  # @see Hash#[]
37
31
  def [](k)
38
- @hash[k]
32
+ case k
33
+ when :reply_code then @basic_return.reply_code
34
+ when :reply_text then @basic_return.reply_text
35
+ when :exchange then @basic_return.exchange
36
+ when :routing_key then @basic_return.routing_key
37
+ else nil
38
+ end
39
39
  end
40
40
 
41
41
  # @return [Hash] Hash representation of this returned delivery info
42
42
  def to_hash
43
- @hash
43
+ @hash ||= {
44
+ :reply_code => @basic_return.reply_code,
45
+ :reply_text => @basic_return.reply_text,
46
+ :exchange => @basic_return.exchange,
47
+ :routing_key => @basic_return.routing_key
48
+ }
44
49
  end
45
50
 
46
51
  # @private
data/lib/bunny/session.rb CHANGED
@@ -8,11 +8,14 @@ require "bunny/transport"
8
8
  require "bunny/channel_id_allocator"
9
9
  require "bunny/heartbeat_sender"
10
10
  require "bunny/reader_loop"
11
+ require "bunny/topology_registry"
12
+ require "bunny/topology_recovery_filter"
11
13
  require "bunny/authentication/credentials_encoder"
12
14
  require "bunny/authentication/plain_mechanism_encoder"
13
15
  require "bunny/authentication/external_mechanism_encoder"
14
16
 
15
17
  require "bunny/concurrent/continuation_queue"
18
+ require "bunny/concurrent/exception_accumulator"
16
19
 
17
20
  require "amq/protocol/client"
18
21
  require "amq/settings"
@@ -60,7 +63,7 @@ module Bunny
60
63
  :product => "Bunny",
61
64
  :platform => ::RUBY_DESCRIPTION,
62
65
  :version => Bunny::VERSION,
63
- :information => "http://rubybunny.info",
66
+ :information => "https://github.com/ruby-amqp/bunny",
64
67
  }
65
68
 
66
69
  # @private
@@ -80,6 +83,8 @@ module Bunny
80
83
  attr_reader :status, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded
81
84
  attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
82
85
  attr_reader :channel_id_allocator
86
+ # @return [Bunny::TopologyRegistry]
87
+ attr_reader :topology_registry
83
88
  # Authentication mechanism, e.g. "PLAIN" or "EXTERNAL"
84
89
  # @return [String]
85
90
  attr_reader :mechanism
@@ -128,8 +133,9 @@ module Bunny
128
133
  # @option connection_string_or_opts [Proc] :recovery_completed (nil) Will be called after successful connection recovery
129
134
  # @option connection_string_or_opts [Proc] :recovery_attempts_exhausted (nil) Will be called when the connection recovery failed after the specified amount of recovery attempts
130
135
  # @option connection_string_or_opts [Boolean] :recover_from_connection_close (true) Should this connection recover after receiving a server-sent connection.close (e.g. connection was force closed)?
131
- # @option connection_string_or_opts [Object] :session_error_handler (Thread.current) Object which responds to #raise that will act as a session error handler. Defaults to Thread.current, which will raise asynchronous exceptions in the thread that created the session.
136
+ # @option connection_string_or_opts [Object] :session_error_handler (ExceptionAccumulator.new) Object which responds to #raise that will act as a session error handler. Defaults to a Bunny::ExceptionAccumulator instance which stores exceptions for safe retrieval later. Can be set to Thread.current for legacy behavior (raises asynchronous exceptions in the creating thread), or any object that responds to #raise.
132
137
  #
138
+ # @option connection_string_or_opts [Bunny::TopologyRecoveryFilter] :topology_recovery_filter if provided, will be used for object filtering during topology recovery
133
139
  # @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL
134
140
  # @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use
135
141
  # @option optz [String] :connection_name (nil) Client-provided connection name, if any. Note that the value returned does not uniquely identify a connection and cannot be used as a connection identifier in HTTP API requests.
@@ -222,11 +228,14 @@ module Bunny
222
228
 
223
229
  @channels = Hash.new
224
230
 
231
+ trf = @opts.fetch(:topology_recovery_filter, DefaultTopologyRecoveryFilter.new)
232
+ @topology_registry = TopologyRegistry.new(topology_recovery_filter: trf)
233
+
225
234
  @recovery_attempt_started = opts[:recovery_attempt_started]
226
235
  @recovery_completed = opts[:recovery_completed]
227
- @recovery_attempts_exhausted = opts[:recovery_attempts_exhausted]
236
+ @recovery_attempts_exhausted = opts[:recovery_attempts_exhausted]
228
237
 
229
- @session_error_handler = opts.fetch(:session_error_handler, Thread.current)
238
+ @session_error_handler = opts.fetch(:session_error_handler, ExceptionAccumulator.new)
230
239
 
231
240
  @recoverable_exceptions = DEFAULT_RECOVERABLE_EXCEPTIONS.dup
232
241
 
@@ -298,7 +307,9 @@ module Bunny
298
307
  def configure_socket(&block)
299
308
  raise ArgumentError, "No block provided!" if block.nil?
300
309
 
301
- @transport.configure_socket(&block)
310
+ @transport_mutex.synchronize do
311
+ @transport.configure_socket(&block)
312
+ end
302
313
  end
303
314
 
304
315
  # @return [Integer] Client socket port
@@ -324,10 +335,11 @@ module Bunny
324
335
  begin
325
336
  # close existing transport if we have one,
326
337
  # to not leak sockets
327
- @transport.maybe_initialize_socket
328
-
329
- @transport.post_initialize_socket
330
- @transport.connect
338
+ @transport_mutex.synchronize do
339
+ @transport.maybe_initialize_socket
340
+ @transport.post_initialize_socket
341
+ @transport.connect
342
+ end
331
343
 
332
344
  self.init_connection
333
345
  self.open_connection
@@ -364,7 +376,7 @@ module Bunny
364
376
  end
365
377
 
366
378
  # Socket operation write timeout used by this connection
367
- # @return [Integer]
379
+ # @return [Float]
368
380
  # @private
369
381
  def transport_write_timeout
370
382
  @transport.write_timeout
@@ -384,7 +396,10 @@ module Bunny
384
396
  if n && (ch = @channels[n])
385
397
  ch
386
398
  else
387
- ch = Bunny::Channel.new(self, n, ConsumerWorkPool.new(consumer_pool_size || 1, consumer_pool_abort_on_exception, consumer_pool_shutdown_timeout))
399
+ work_pool = ConsumerWorkPool.new(consumer_pool_size || 1, consumer_pool_abort_on_exception, consumer_pool_shutdown_timeout)
400
+ ch = Bunny::Channel.new(self, n, {
401
+ work_pool: work_pool
402
+ })
388
403
  ch.open
389
404
  ch
390
405
  end
@@ -493,9 +508,59 @@ module Bunny
493
508
  @blocked
494
509
  end
495
510
 
511
+ # Returns the session error handler.
512
+ # By default, this is a Bunny::ExceptionAccumulator instance.
513
+ #
514
+ # @return [Object] the session error handler
515
+ # @api public
516
+ attr_reader :session_error_handler
517
+
518
+ # Returns true if any exceptions have been accumulated by the session error handler.
519
+ # Only works when using the default ExceptionAccumulator handler.
520
+ #
521
+ # @return [Boolean] true if exceptions have been accumulated
522
+ # @raise [NoMethodError] if the session error handler doesn't respond to #any?
523
+ # @api public
524
+ def exception_occurred?
525
+ @session_error_handler.any?
526
+ end
527
+
528
+ # Returns all accumulated exceptions from the session error handler.
529
+ # Only works when using the default ExceptionAccumulator handler.
530
+ #
531
+ # @return [Array<Exception>] all accumulated exceptions
532
+ # @raise [NoMethodError] if the session error handler doesn't respond to #all
533
+ # @api public
534
+ def exceptions
535
+ @session_error_handler.all
536
+ end
537
+
538
+ # Clears all accumulated exceptions from the session error handler.
539
+ # Only works when using the default ExceptionAccumulator handler.
540
+ #
541
+ # @return [Array<Exception>] the exceptions that were cleared
542
+ # @raise [NoMethodError] if the session error handler doesn't respond to #clear
543
+ # @api public
544
+ def clear_exceptions
545
+ @session_error_handler.clear
546
+ end
547
+
548
+ # Raises the first accumulated exception if any exist.
549
+ # Only works when using the default ExceptionAccumulator handler.
550
+ #
551
+ # This is useful for checking errors at safe points in your application,
552
+ # such as after completing a batch of operations.
553
+ #
554
+ # @raise [Exception] the first accumulated exception
555
+ # @raise [NoMethodError] if the session error handler doesn't respond to #raise_first!
556
+ # @api public
557
+ def raise_on_exception!
558
+ @session_error_handler.raise_first!
559
+ end
560
+
496
561
  # Parses an amqp[s] URI into a hash that {Bunny::Session#initialize} accepts.
497
562
  #
498
- # @param [String] uri amqp or amqps URI to parse
563
+ # @param [String | Hash] uri amqp or amqps URI to parse
499
564
  # @return [Hash] Parsed URI as a hash
500
565
  def self.parse_uri(uri)
501
566
  AMQ::Settings.configure(uri)
@@ -562,6 +627,17 @@ module Bunny
562
627
  @recovery_attempts_exhausted = block
563
628
  end
564
629
 
630
+ # Recovers topology (exchanges, queues, bindings, consumers) for a single channel.
631
+ # Intended for use after {Bunny::Channel#reopen} to restore the channel to a
632
+ # working state with its original consumers.
633
+ #
634
+ # @param [Bunny::Channel] ch Channel whose topology should be recovered
635
+ # @api public
636
+ def recover_channel_topology(ch)
637
+ filter = Proc.new { |entity| entity.channel == ch }
638
+ recover_topology_with(filter)
639
+ end
640
+
565
641
  #
566
642
  # Implementation
567
643
  #
@@ -747,7 +823,9 @@ module Bunny
747
823
  @reader_loop.stop if @reader_loop
748
824
  maybe_shutdown_heartbeat_sender
749
825
 
750
- recover_from_network_failure
826
+ recover_connection_and_channels
827
+ recover_topology
828
+ notify_of_recovery_completion
751
829
  else
752
830
  @logger.error "Exception #{exception.message} is considered unrecoverable..."
753
831
  end
@@ -757,16 +835,135 @@ module Bunny
757
835
  end
758
836
  end
759
837
 
838
+ # @return [Boolean]
760
839
  # @private
761
840
  def recoverable_network_failure?(exception)
762
841
  @recoverable_exceptions.any? {|x| exception.kind_of? x}
763
842
  end
764
843
 
844
+ # @return [Boolean]
765
845
  # @private
766
846
  def recovering_from_network_failure?
767
847
  @recovering_from_network_failure
768
848
  end
769
849
 
850
+ # @param [Bunny::Queue] queue
851
+ # @private
852
+ def record_queue(queue)
853
+ @topology_registry.record_queue(queue)
854
+ end
855
+
856
+ # @param [Bunny::Channel] ch
857
+ # @param [String] name
858
+ # @param [Boolean] server_named
859
+ # @param [Boolean] durable
860
+ # @param [Boolean] auto_delete
861
+ # @param [Boolean] exclusive
862
+ # @param [Hash] arguments
863
+ def record_queue_with(ch, name, server_named, durable, auto_delete, exclusive, arguments)
864
+ @topology_registry.record_queue_with(ch, name, server_named, durable, auto_delete, exclusive, arguments)
865
+ end
866
+
867
+ # @param [Bunny::Queue, Bunny::RecordedQueue] queue
868
+ # @private
869
+ def delete_recoreded_queue(queue)
870
+ @topology_registry.delete_recorded_queue(queue)
871
+ end
872
+
873
+ # @param [String] name
874
+ # @private
875
+ def delete_recorded_queue_named(name)
876
+ @topology_registry.delete_recorded_queue_named(name)
877
+ end
878
+
879
+ # @param [Bunny::Exchange] exchange
880
+ # @private
881
+ def record_exchange(exchange)
882
+ @topology_registry.record_exchange(exchange)
883
+ end
884
+
885
+ # @param [Bunny::Channel] ch
886
+ # @param [String] name
887
+ # @param [String] type
888
+ # @param [Boolean] durable
889
+ # @param [Boolean] auto_delete
890
+ # @param [Hash] arguments
891
+ def record_exchange_with(ch, name, type, durable, auto_delete, arguments)
892
+ @topology_registry.record_exchange_with(ch, name, type, durable, auto_delete, arguments)
893
+ end
894
+
895
+ # @param [Bunny::Exchange] exchange
896
+ # @private
897
+ def delete_recorded_exchange(exchange)
898
+ @topology_registry.delete_recorded_exchange(exchange)
899
+ end
900
+
901
+ # @param [String] name
902
+ # @private
903
+ def delete_recorded_exchange_named(name)
904
+ @topology_registry.delete_recorded_exchange_named(name)
905
+ end
906
+
907
+ # @param [Bunny::Channel] ch
908
+ # @param [String] exchange_name
909
+ # @param [String] queue_name
910
+ # @param [String] routing_key
911
+ # @param [Hash] arguments
912
+ # @private
913
+ def record_queue_binding_with(ch, exchange_name, queue_name, routing_key, arguments)
914
+ @topology_registry.record_queue_binding_with(ch, exchange_name, queue_name, routing_key, arguments)
915
+ end
916
+
917
+ # @param [Bunny::Channel] ch
918
+ # @param [String] exchange_name
919
+ # @param [String] queue_name
920
+ # @param [String] routing_key
921
+ # @param [Hash] arguments
922
+ # @private
923
+ def delete_recorded_queue_binding(ch, exchange_name, queue_name, routing_key, arguments)
924
+ @topology_registry.delete_recorded_queue_binding(ch, exchange_name, queue_name, routing_key, arguments)
925
+ end
926
+
927
+ # @param [Bunny::Channel] ch
928
+ # @param [String] source_name
929
+ # @param [String] destination_name
930
+ # @param [String] routing_key
931
+ # @param [Hash] arguments
932
+ # @private
933
+ def record_exchange_binding_with(ch, source_name, destination_name, routing_key, arguments)
934
+ @topology_registry.record_exchange_binding_with(ch, source_name, destination_name, routing_key, arguments)
935
+ end
936
+
937
+ # @param [Bunny::Channel] ch
938
+ # @param [String] source_name
939
+ # @param [String] destination_name
940
+ # @param [String] routing_key
941
+ # @param [Hash] arguments
942
+ # @private
943
+ def delete_recorded_exchange_binding(ch, source_name, destination_name, routing_key, arguments)
944
+ @topology_registry.delete_recorded_exchange_binding(ch, source_name, destination_name, routing_key, arguments)
945
+ end
946
+
947
+ # @param [Bunny::Channel] ch
948
+ # @param [String] consumer_tag
949
+ # @param [String] queue_name
950
+ # @param [#call] callable
951
+ # @param [Boolean] manual_ack
952
+ # @param [Boolean] exclusive
953
+ # @param [Hash] arguments
954
+ # @private
955
+ def record_consumer_with(ch, consumer_tag, queue_name, callable, manual_ack, exclusive, arguments)
956
+ @topology_registry.record_consumer_with(ch, consumer_tag, queue_name, callable, manual_ack, exclusive, arguments)
957
+ end
958
+
959
+ # @param [String] consumer_tag
960
+ # @private
961
+ def delete_recorded_consumer(consumer_tag)
962
+ @topology_registry.delete_recorded_consumer(consumer_tag)
963
+ end
964
+
965
+
966
+
770
967
  # @private
771
968
  def announce_network_failure_recovery
772
969
  if recovery_attempts_limited?
@@ -777,18 +974,17 @@ module Bunny
777
974
  end
778
975
 
779
976
  # @private
780
- def recover_from_network_failure
977
+ def recover_connection_and_channels
781
978
  sleep @network_recovery_interval
782
979
  @logger.debug "Will attempt connection recovery..."
783
980
  notify_of_recovery_attempt_start
784
981
 
785
982
  self.initialize_transport
786
983
 
787
- @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
984
+ @logger.debug "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
788
985
  self.start
789
986
 
790
987
  if open?
791
-
792
988
  @recovering_from_network_failure = false
793
989
  @logger.debug "Connection is now open"
794
990
  if @reset_recovery_attempt_counter_after_reconnection
@@ -799,7 +995,6 @@ module Bunny
799
995
  end
800
996
 
801
997
  recover_channels
802
- notify_of_recovery_completion
803
998
  end
804
999
  rescue HostListDepleted
805
1000
  reset_address_index
@@ -858,6 +1053,141 @@ module Bunny
858
1053
  end
859
1054
  end
860
1055
 
1056
+ # @private
1057
+ def recover_topology
1058
+ @logger.debug "Will recover topology now"
1059
+ recover_topology_with
1060
+ end
1061
+
1062
+ # @private
1063
+ def recover_topology_with(filter = nil)
1064
+ exchanges = @topology_registry.filtered_exchanges.reject(&:predeclared?)
1065
+ queues = @topology_registry.filtered_queues
1066
+ queue_bindings = @topology_registry.filtered_queue_bindings
1067
+ exchange_bindings = @topology_registry.filtered_exchange_bindings
1068
+ consumers = @topology_registry.filtered_consumers
1069
+
1070
+ if filter
1071
+ exchanges = exchanges.select(&filter)
1072
+ queues = queues.select(&filter)
1073
+ queue_bindings = queue_bindings.select(&filter)
1074
+ exchange_bindings = exchange_bindings.select(&filter)
1075
+ consumers = consumers.select(&filter)
1076
+ end
1077
+
1078
+ @logger.debug { "Will recover #{exchanges.size} exchange(s)" }
1079
+ exchanges.each do |x|
1080
+ begin
1081
+ recover_exchange(x)
1082
+ rescue Exception => e
1083
+ @logger.error "Caught an exception while recovering exchange #{x.name}: #{e.inspect}"
1084
+ end
1085
+ end
1086
+
1087
+ @logger.debug { "Will recover #{queues.size} queue(s)" }
1088
+ queues.each do |q|
1089
+ begin
1090
+ recover_queue(q)
1091
+ rescue Exception => e
1092
+ @logger.error "Caught an exception while recovering queue #{q.name}: #{e.inspect}"
1093
+ end
1094
+ end
1095
+
1096
+ @logger.debug { "Will recover #{queue_bindings.size + exchange_bindings.size} binding(s)" }
1097
+ queue_bindings.each do |b|
1098
+ begin
1099
+ recover_queue_binding(b)
1100
+ rescue Exception => e
1101
+ @logger.error "Caught an exception while recovering a binding of queue #{b.destination}: #{e.inspect}"
1102
+ end
1103
+ end
1104
+
1105
+ exchange_bindings.each do |b|
1106
+ begin
1107
+ recover_exchange_binding(b)
1108
+ rescue Exception => e
1109
+ @logger.error "Caught an exception while recovering a binding of exchange #{b.source}: #{e.inspect}"
1110
+ end
1111
+ end
1112
+
1113
+ @logger.debug { "Will recover #{consumers.size} consumer(s)" }
1114
+ consumers.each do |c|
1115
+ recover_consumer(c)
1116
+ end
1117
+ end
1118
+
1119
+ # @param [Bunny::RecordedExchange] x
1120
+ # @private
1121
+ def recover_exchange(x)
1122
+ opts = {
1123
+ durable: x.durable,
1124
+ auto_delete: x.auto_delete,
1125
+ arguments: x.arguments
1126
+ }
1127
+ x.channel.exchange_declare(x.name, x.type, opts)
1128
+ end
1129
+
1130
+ # @param [Bunny::RecordedQueue] q
1131
+ # @private
1132
+ def recover_queue(q)
1133
+ opts = {
1134
+ durable: q.durable,
1135
+ auto_delete: q.auto_delete,
1136
+ exclusive: q.exclusive,
1137
+ arguments: q.arguments
1138
+ }
1139
+
1140
+ old_name = q.name
1141
+ # this response carries the server-generated name
1142
+ queue_declare_ok = q.channel.queue_declare(q.name_to_use_for_recovery, opts)
1143
+ new_name = queue_declare_ok.queue
1144
+
1145
+ # if the name has changed, update all the bindings where
1146
+ # this queue is the destination, then all consumers
1147
+ if new_name != old_name
1148
+ record_queue_name_change(old_name, new_name)
1149
+ q.channel.record_queue_name_change(old_name, new_name)
1150
+ end
1151
+ end
1152
+
1153
+ # @param [String] old_name
1154
+ # @param [String] new_name
1155
+ # @private
1156
+ def record_queue_name_change(old_name, new_name)
1157
+ @topology_registry.record_queue_name_change(old_name, new_name)
1158
+ end
1159
+
1160
+ # @param [Bunny::RecordedQueueBinding] rb
1161
+ # @private
1162
+ def recover_queue_binding(rb)
1163
+ opts = {
1164
+ routing_key: rb.routing_key,
1165
+ arguments: rb.arguments
1166
+ }
1167
+
1168
+ rb.channel.queue_bind_without_recording_topology(rb.destination, rb.source, opts)
1169
+ end
1170
+
1171
+ # @param [Bunny::RecordedExchangeBindingBinding] rb
1172
+ # @private
1173
+ def recover_exchange_binding(rb)
1174
+ opts = {
1175
+ routing_key: rb.routing_key,
1176
+ arguments: rb.arguments
1177
+ }
1178
+
1179
+ rb.channel.exchange_bind_without_recording_topology(rb.source, rb.destination, opts)
1180
+ end
1181
+
1182
+ # @param [Bunny::RecordedConsumer] c
1183
+ # @private
1184
+ def recover_consumer(c)
1185
+ c.channel.maybe_reinitialize_consumer_pool!
1186
+ c.channel.basic_consume(c.queue_name, c.consumer_tag, !c.manual_ack, c.exclusive, c.arguments) do |*args|
1187
+ c.callable.call(*args)
1188
+ end
1189
+ end
1190
+
861
1191
  # @private
862
1192
  def notify_of_recovery_attempt_start
863
1193
  @recovery_attempt_started.call if @recovery_attempt_started
@@ -1172,7 +1502,8 @@ module Bunny
1172
1502
  # locking. See a note about "single frame" methods in a comment in `send_frameset`. MK.
1173
1503
  channel.synchronize do
1174
1504
  if open?
1175
- frames.each { |frame| self.send_frame_without_timeout(frame, false) }
1505
+ data = frames.reduce(+"") { |acc, frame| acc << frame.encode }
1506
+ @transport.write_without_timeout(data)
1176
1507
  signal_activity!
1177
1508
  else
1178
1509
  raise ConnectionClosedError.new(frames)
@@ -1188,10 +1519,14 @@ module Bunny
1188
1519
  # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
1189
1520
  # locking. Note that "single frame" methods do not need this kind of synchronization. MK.
1190
1521
  channel.synchronize do
1191
- @transport.write(data)
1192
- signal_activity!
1522
+ if open?
1523
+ @transport.write(data)
1524
+ signal_activity!
1525
+ else
1526
+ raise ConnectionClosedError.new("pre-encoded data (#{data.bytesize} bytes)")
1527
+ end
1193
1528
  end
1194
- end # send_frameset_without_timeout(frames)
1529
+ end # send_raw_without_timeout(data)
1195
1530
 
1196
1531
  # @return [String]
1197
1532
  # @api public
@@ -1359,19 +1694,22 @@ module Bunny
1359
1694
 
1360
1695
  # @private
1361
1696
  def initialize_transport
1362
- if address = @addresses[ @address_index ]
1363
- @address_index_mutex.synchronize { @address_index += 1 }
1364
- @transport.close rescue nil # Let's make sure the previous transport socket is closed
1365
- @transport = Transport.new(self,
1366
- host_from_address(address),
1367
- port_from_address(address),
1368
- @opts.merge(:session_error_handler => @session_error_handler)
1369
- )
1370
-
1371
- # Reset the cached progname for the logger only when no logger was provided
1372
- @default_logger.progname = self.to_s
1373
-
1374
- @transport
1697
+ address = @addresses[@address_index]
1698
+ if address
1699
+ @transport_mutex.synchronize do
1700
+ @address_index_mutex.synchronize { @address_index += 1 }
1701
+ @transport.close rescue nil # Let's make sure the previous transport socket is closed
1702
+ @transport = Transport.new(self,
1703
+ host_from_address(address),
1704
+ port_from_address(address),
1705
+ @opts.merge(:session_error_handler => @session_error_handler)
1706
+ )
1707
+
1708
+ # Reset the cached progname for the logger only when no logger was provided
1709
+ @default_logger.progname = self.to_s
1710
+
1711
+ @transport
1712
+ end
1375
1713
  else
1376
1714
  raise HostListDepleted
1377
1715
  end
@@ -1379,7 +1717,9 @@ module Bunny
1379
1717
 
1380
1718
  # @private
1381
1719
  def maybe_close_transport
1382
- @transport.close if @transport
1720
+ @transport_mutex.synchronize do
1721
+ @transport.close if @transport
1722
+ end
1383
1723
  end
1384
1724
 
1385
1725
  # Sends AMQ protocol header (also known as preamble).
@@ -1393,7 +1733,7 @@ module Bunny
1393
1733
  # @private
1394
1734
  def encode_credentials(username, password)
1395
1735
  @credentials_encoder.encode_credentials(username, password)
1396
- end # encode_credentials(username, password)
1736
+ end
1397
1737
 
1398
1738
  # @private
1399
1739
  def credentials_encoder_for(mechanism)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bunny
4
- # Abstracts away the Ruby (OS) method of retriving timestamps.
4
+ # Abstracts away the Ruby (OS) method of retrieving timestamps.
5
5
  #
6
6
  # @private
7
7
  class Timestamp