bunny 2.23.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -36
  3. data/lib/amq/protocol/extensions.rb +2 -0
  4. data/lib/bunny/authentication/credentials_encoder.rb +2 -0
  5. data/lib/bunny/authentication/external_mechanism_encoder.rb +2 -0
  6. data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
  7. data/lib/bunny/channel.rb +778 -150
  8. data/lib/bunny/channel_id_allocator.rb +2 -0
  9. data/lib/bunny/concurrent/atomic_fixnum.rb +2 -0
  10. data/lib/bunny/concurrent/condition.rb +2 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +2 -0
  12. data/lib/bunny/concurrent/exception_accumulator.rb +115 -0
  13. data/lib/bunny/concurrent/synchronized_sorted_set.rb +2 -0
  14. data/lib/bunny/consumer.rb +4 -11
  15. data/lib/bunny/consumer_tag_generator.rb +2 -0
  16. data/lib/bunny/consumer_work_pool.rb +2 -0
  17. data/lib/bunny/cruby/socket.rb +36 -2
  18. data/lib/bunny/cruby/ssl_socket.rb +44 -1
  19. data/lib/bunny/delivery_info.rb +23 -15
  20. data/lib/bunny/exceptions.rb +33 -2
  21. data/lib/bunny/exchange.rb +27 -13
  22. data/lib/bunny/framing.rb +2 -0
  23. data/lib/bunny/get_response.rb +20 -14
  24. data/lib/bunny/heartbeat_sender.rb +4 -2
  25. data/lib/bunny/message_properties.rb +2 -0
  26. data/lib/bunny/queue.rb +31 -39
  27. data/lib/bunny/reader_loop.rb +9 -7
  28. data/lib/bunny/return_info.rb +18 -11
  29. data/lib/bunny/session.rb +387 -63
  30. data/lib/bunny/socket.rb +7 -12
  31. data/lib/bunny/ssl_socket.rb +7 -12
  32. data/lib/bunny/test_kit.rb +1 -0
  33. data/lib/bunny/timeout.rb +2 -0
  34. data/lib/bunny/timestamp.rb +3 -1
  35. data/lib/bunny/topology_recovery_filter.rb +71 -0
  36. data/lib/bunny/topology_registry.rb +824 -0
  37. data/lib/bunny/transport.rb +54 -49
  38. data/lib/bunny/version.rb +2 -1
  39. data/lib/bunny.rb +2 -1
  40. metadata +25 -14
  41. data/lib/bunny/concurrent/linked_continuation_queue.rb +0 -61
  42. data/lib/bunny/jruby/socket.rb +0 -57
  43. data/lib/bunny/jruby/ssl_socket.rb +0 -58
  44. data/lib/bunny/versioned_delivery_tag.rb +0 -28
data/lib/bunny/session.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "socket"
2
4
  require "thread"
3
5
  require "monitor"
@@ -6,15 +8,14 @@ require "bunny/transport"
6
8
  require "bunny/channel_id_allocator"
7
9
  require "bunny/heartbeat_sender"
8
10
  require "bunny/reader_loop"
11
+ require "bunny/topology_registry"
12
+ require "bunny/topology_recovery_filter"
9
13
  require "bunny/authentication/credentials_encoder"
10
14
  require "bunny/authentication/plain_mechanism_encoder"
11
15
  require "bunny/authentication/external_mechanism_encoder"
12
16
 
13
- if defined?(JRUBY_VERSION)
14
- require "bunny/concurrent/linked_continuation_queue"
15
- else
16
- require "bunny/concurrent/continuation_queue"
17
- end
17
+ require "bunny/concurrent/continuation_queue"
18
+ require "bunny/concurrent/exception_accumulator"
18
19
 
19
20
  require "amq/protocol/client"
20
21
  require "amq/settings"
@@ -62,7 +63,7 @@ module Bunny
62
63
  :product => "Bunny",
63
64
  :platform => ::RUBY_DESCRIPTION,
64
65
  :version => Bunny::VERSION,
65
- :information => "http://rubybunny.info",
66
+ :information => "https://github.com/ruby-amqp/bunny",
66
67
  }
67
68
 
68
69
  # @private
@@ -82,6 +83,8 @@ module Bunny
82
83
  attr_reader :status, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded
83
84
  attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
84
85
  attr_reader :channel_id_allocator
86
+ # @return [Bunny::TopologyRegistry]
87
+ attr_reader :topology_registry
85
88
  # Authentication mechanism, e.g. "PLAIN" or "EXTERNAL"
86
89
  # @return [String]
87
90
  attr_reader :mechanism
@@ -130,8 +133,9 @@ module Bunny
130
133
  # @option connection_string_or_opts [Proc] :recovery_completed (nil) Will be called after successful connection recovery
131
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
132
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)?
133
- # @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.
134
137
  #
138
+ # @option connection_string_or_opts [Bunny::TopologyRecoveryFilter] :topology_recovery_filter if provided, will be used for object filtering during topology recovery
135
139
  # @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL
136
140
  # @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use
137
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.
@@ -224,11 +228,14 @@ module Bunny
224
228
 
225
229
  @channels = Hash.new
226
230
 
231
+ trf = @opts.fetch(:topology_recovery_filter, DefaultTopologyRecoveryFilter.new)
232
+ @topology_registry = TopologyRegistry.new(topology_recovery_filter: trf)
233
+
227
234
  @recovery_attempt_started = opts[:recovery_attempt_started]
228
235
  @recovery_completed = opts[:recovery_completed]
229
- @recovery_attempts_exhausted = opts[:recovery_attempts_exhausted]
236
+ @recovery_attempts_exhausted = opts[:recovery_attempts_exhausted]
230
237
 
231
- @session_error_handler = opts.fetch(:session_error_handler, Thread.current)
238
+ @session_error_handler = opts.fetch(:session_error_handler, ExceptionAccumulator.new)
232
239
 
233
240
  @recoverable_exceptions = DEFAULT_RECOVERABLE_EXCEPTIONS.dup
234
241
 
@@ -300,7 +307,9 @@ module Bunny
300
307
  def configure_socket(&block)
301
308
  raise ArgumentError, "No block provided!" if block.nil?
302
309
 
303
- @transport.configure_socket(&block)
310
+ @transport_mutex.synchronize do
311
+ @transport.configure_socket(&block)
312
+ end
304
313
  end
305
314
 
306
315
  # @return [Integer] Client socket port
@@ -326,10 +335,11 @@ module Bunny
326
335
  begin
327
336
  # close existing transport if we have one,
328
337
  # to not leak sockets
329
- @transport.maybe_initialize_socket
330
-
331
- @transport.post_initialize_socket
332
- @transport.connect
338
+ @transport_mutex.synchronize do
339
+ @transport.maybe_initialize_socket
340
+ @transport.post_initialize_socket
341
+ @transport.connect
342
+ end
333
343
 
334
344
  self.init_connection
335
345
  self.open_connection
@@ -366,7 +376,7 @@ module Bunny
366
376
  end
367
377
 
368
378
  # Socket operation write timeout used by this connection
369
- # @return [Integer]
379
+ # @return [Float]
370
380
  # @private
371
381
  def transport_write_timeout
372
382
  @transport.write_timeout
@@ -386,7 +396,10 @@ module Bunny
386
396
  if n && (ch = @channels[n])
387
397
  ch
388
398
  else
389
- 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
+ })
390
403
  ch.open
391
404
  ch
392
405
  end
@@ -495,9 +508,59 @@ module Bunny
495
508
  @blocked
496
509
  end
497
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
+
498
561
  # Parses an amqp[s] URI into a hash that {Bunny::Session#initialize} accepts.
499
562
  #
500
- # @param [String] uri amqp or amqps URI to parse
563
+ # @param [String | Hash] uri amqp or amqps URI to parse
501
564
  # @return [Hash] Parsed URI as a hash
502
565
  def self.parse_uri(uri)
503
566
  AMQ::Settings.configure(uri)
@@ -516,6 +579,8 @@ module Bunny
516
579
  begin
517
580
  ch.queue(name, :passive => true)
518
581
  true
582
+ rescue Bunny::ResourceLocked => _
583
+ true
519
584
  rescue Bunny::NotFound => _
520
585
  false
521
586
  ensure
@@ -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
@@ -1076,16 +1406,7 @@ module Bunny
1076
1406
  # this is the easiest way to wait until the loop
1077
1407
  # is guaranteed to have terminated
1078
1408
  @reader_loop.terminate_with(ShutdownSignal)
1079
- # joining the thread here may take forever
1080
- # on JRuby because sun.nio.ch.KQueueArrayWrapper#kevent0 is
1081
- # a native method that cannot be (easily) interrupted.
1082
- # So we use this ugly hack or else our test suite takes forever
1083
- # to run on JRuby (a new connection is opened/closed per example). MK.
1084
- if defined?(JRUBY_VERSION)
1085
- sleep 0.075
1086
- else
1087
- @reader_loop.join
1088
- end
1409
+ @reader_loop.join
1089
1410
  else
1090
1411
  # single threaded mode, nothing to do. MK.
1091
1412
  end
@@ -1159,7 +1480,7 @@ module Bunny
1159
1480
  channel.synchronize do
1160
1481
  # see rabbitmq/rabbitmq-server#156
1161
1482
  if open?
1162
- data = frames.reduce("") { |acc, frame| acc << frame.encode }
1483
+ data = frames.reduce(+"") { |acc, frame| acc << frame.encode }
1163
1484
  @transport.write(data)
1164
1485
  signal_activity!
1165
1486
  else
@@ -1181,7 +1502,8 @@ module Bunny
1181
1502
  # locking. See a note about "single frame" methods in a comment in `send_frameset`. MK.
1182
1503
  channel.synchronize do
1183
1504
  if open?
1184
- 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)
1185
1507
  signal_activity!
1186
1508
  else
1187
1509
  raise ConnectionClosedError.new(frames)
@@ -1197,10 +1519,14 @@ module Bunny
1197
1519
  # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
1198
1520
  # locking. Note that "single frame" methods do not need this kind of synchronization. MK.
1199
1521
  channel.synchronize do
1200
- @transport.write(data)
1201
- 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
1202
1528
  end
1203
- end # send_frameset_without_timeout(frames)
1529
+ end # send_raw_without_timeout(data)
1204
1530
 
1205
1531
  # @return [String]
1206
1532
  # @api public
@@ -1270,7 +1596,7 @@ module Bunny
1270
1596
  negotiate_value(@client_heartbeat, connection_tune.heartbeat)
1271
1597
  end
1272
1598
  @logger.debug { "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}" }
1273
- @logger.info "Heartbeat interval used (in seconds): #{@heartbeat}"
1599
+ @logger.debug "Heartbeat interval used (in seconds): #{@heartbeat}"
1274
1600
 
1275
1601
  # We set the read_write_timeout to twice the heartbeat value,
1276
1602
  # and then some padding for edge cases.
@@ -1368,19 +1694,22 @@ module Bunny
1368
1694
 
1369
1695
  # @private
1370
1696
  def initialize_transport
1371
- if address = @addresses[ @address_index ]
1372
- @address_index_mutex.synchronize { @address_index += 1 }
1373
- @transport.close rescue nil # Let's make sure the previous transport socket is closed
1374
- @transport = Transport.new(self,
1375
- host_from_address(address),
1376
- port_from_address(address),
1377
- @opts.merge(:session_error_handler => @session_error_handler)
1378
- )
1379
-
1380
- # Reset the cached progname for the logger only when no logger was provided
1381
- @default_logger.progname = self.to_s
1382
-
1383
- @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
1384
1713
  else
1385
1714
  raise HostListDepleted
1386
1715
  end
@@ -1388,7 +1717,9 @@ module Bunny
1388
1717
 
1389
1718
  # @private
1390
1719
  def maybe_close_transport
1391
- @transport.close if @transport
1720
+ @transport_mutex.synchronize do
1721
+ @transport.close if @transport
1722
+ end
1392
1723
  end
1393
1724
 
1394
1725
  # Sends AMQ protocol header (also known as preamble).
@@ -1402,23 +1733,16 @@ module Bunny
1402
1733
  # @private
1403
1734
  def encode_credentials(username, password)
1404
1735
  @credentials_encoder.encode_credentials(username, password)
1405
- end # encode_credentials(username, password)
1736
+ end
1406
1737
 
1407
1738
  # @private
1408
1739
  def credentials_encoder_for(mechanism)
1409
1740
  Authentication::CredentialsEncoder.for_session(self)
1410
1741
  end
1411
1742
 
1412
- if defined?(JRUBY_VERSION)
1413
- # @private
1414
- def reset_continuations
1415
- @continuations = Concurrent::LinkedContinuationQueue.new
1416
- end
1417
- else
1418
- # @private
1419
- def reset_continuations
1420
- @continuations = Concurrent::ContinuationQueue.new
1421
- end
1743
+ # @private
1744
+ def reset_continuations
1745
+ @continuations = Concurrent::ContinuationQueue.new
1422
1746
  end
1423
1747
 
1424
1748
  # @private
data/lib/bunny/socket.rb CHANGED
@@ -1,14 +1,9 @@
1
- # See #165. MK.
2
- if defined?(JRUBY_VERSION)
3
- require "bunny/jruby/socket"
1
+ # frozen_string_literal: true
4
2
 
5
- module Bunny
6
- SocketImpl = JRuby::Socket
7
- end
8
- else
9
- require "bunny/cruby/socket"
3
+ require "bunny/cruby/socket"
10
4
 
11
- module Bunny
12
- SocketImpl = Socket
13
- end
14
- end
5
+ module Bunny
6
+ # An alias for the standard MRI Socket,
7
+ # exists from the days of JRuby support.
8
+ SocketImpl = Socket
9
+ end