bunny 2.24.0 → 3.1.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.
- checksums.yaml +4 -4
- data/README.md +15 -9
- data/lib/bunny/channel.rb +727 -124
- data/lib/bunny/concurrent/exception_accumulator.rb +115 -0
- data/lib/bunny/consumer.rb +2 -11
- data/lib/bunny/cruby/socket.rb +33 -1
- data/lib/bunny/cruby/ssl_socket.rb +41 -0
- data/lib/bunny/delivery_info.rb +22 -16
- data/lib/bunny/exceptions.rb +31 -2
- data/lib/bunny/exchange.rb +25 -13
- data/lib/bunny/get_response.rb +19 -15
- data/lib/bunny/heartbeat_sender.rb +2 -2
- data/lib/bunny/queue.rb +22 -38
- data/lib/bunny/reader_loop.rb +6 -6
- data/lib/bunny/return_info.rb +16 -11
- data/lib/bunny/session.rb +388 -36
- data/lib/bunny/timestamp.rb +1 -1
- data/lib/bunny/topology_recovery_filter.rb +71 -0
- data/lib/bunny/topology_registry.rb +824 -0
- data/lib/bunny/transport.rb +36 -9
- data/lib/bunny/version.rb +1 -1
- data/lib/bunny.rb +1 -1
- metadata +29 -7
- data/lib/bunny/versioned_delivery_tag.rb +0 -30
data/lib/bunny/return_info.rb
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Bunny
|
|
4
4
|
# Wraps AMQ::Protocol::Basic::Return to
|
|
5
|
-
# provide access to the
|
|
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
|
-
|
|
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
|
-
|
|
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 => "
|
|
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 (
|
|
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
|
|
236
|
+
@recovery_attempts_exhausted = opts[:recovery_attempts_exhausted]
|
|
228
237
|
|
|
229
|
-
@session_error_handler = opts.fetch(:session_error_handler,
|
|
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
|
-
@
|
|
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
|
-
@
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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 [
|
|
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
|
-
|
|
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
|
#
|
|
@@ -741,13 +817,17 @@ module Bunny
|
|
|
741
817
|
announce_network_failure_recovery
|
|
742
818
|
@channel_mutex.synchronize do
|
|
743
819
|
@channels.each do |n, ch|
|
|
820
|
+
ch.connection_closed!
|
|
744
821
|
ch.maybe_kill_consumer_work_pool!
|
|
745
822
|
end
|
|
746
823
|
end
|
|
747
824
|
@reader_loop.stop if @reader_loop
|
|
748
825
|
maybe_shutdown_heartbeat_sender
|
|
749
826
|
|
|
750
|
-
|
|
827
|
+
recover_connection_and_channels
|
|
828
|
+
recover_topology
|
|
829
|
+
mark_channels_after_recovery!
|
|
830
|
+
notify_of_recovery_completion
|
|
751
831
|
else
|
|
752
832
|
@logger.error "Exception #{exception.message} is considered unrecoverable..."
|
|
753
833
|
end
|
|
@@ -757,16 +837,135 @@ module Bunny
|
|
|
757
837
|
end
|
|
758
838
|
end
|
|
759
839
|
|
|
840
|
+
# @return [Boolean]
|
|
760
841
|
# @private
|
|
761
842
|
def recoverable_network_failure?(exception)
|
|
762
843
|
@recoverable_exceptions.any? {|x| exception.kind_of? x}
|
|
763
844
|
end
|
|
764
845
|
|
|
846
|
+
# @return [Boolean]
|
|
765
847
|
# @private
|
|
766
848
|
def recovering_from_network_failure?
|
|
767
849
|
@recovering_from_network_failure
|
|
768
850
|
end
|
|
769
851
|
|
|
852
|
+
# @param [Bunny::Queue] queue
|
|
853
|
+
# @private
|
|
854
|
+
def record_queue(queue)
|
|
855
|
+
@topology_registry.record_queue(queue)
|
|
856
|
+
end
|
|
857
|
+
|
|
858
|
+
# @param [Bunny::Channel] ch
|
|
859
|
+
# @param [String] name
|
|
860
|
+
# @param [Boolean] server_named
|
|
861
|
+
# @param [Boolean] durable
|
|
862
|
+
# @param [Boolean] auto_delete
|
|
863
|
+
# @param [Boolean] exclusive
|
|
864
|
+
# @param [Hash] arguments
|
|
865
|
+
def record_queue_with(ch, name, server_named, durable, auto_delete, exclusive, arguments)
|
|
866
|
+
@topology_registry.record_queue_with(ch, name, server_named, durable, auto_delete, exclusive, arguments)
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
# @param [Bunny::Queue, Bunny::RecordedQueue] queue
|
|
870
|
+
# @private
|
|
871
|
+
def delete_recoreded_queue(queue)
|
|
872
|
+
@topology_registry.delete_recorded_queue(queue)
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
# @param [String] name
|
|
876
|
+
# @private
|
|
877
|
+
def delete_recorded_queue_named(name)
|
|
878
|
+
@topology_registry.delete_recorded_queue_named(name)
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# @param [Bunny::Exchange] exchange
|
|
882
|
+
# @private
|
|
883
|
+
def record_exchange(exchange)
|
|
884
|
+
@topology_registry.record_exchange(exchange)
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
# @param [Bunny::Channel] ch
|
|
888
|
+
# @param [String] name
|
|
889
|
+
# @param [String] type
|
|
890
|
+
# @param [Boolean] durable
|
|
891
|
+
# @param [Boolean] auto_delete
|
|
892
|
+
# @param [Hash] arguments
|
|
893
|
+
def record_exchange_with(ch, name, type, durable, auto_delete, arguments)
|
|
894
|
+
@topology_registry.record_exchange_with(ch, name, type, durable, auto_delete, arguments)
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# @param [Bunny::Exchange] exchange
|
|
898
|
+
# @private
|
|
899
|
+
def delete_recorded_exchange(exchange)
|
|
900
|
+
@topology_registry.delete_recorded_exchange(exchange)
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
# @param [String] name
|
|
904
|
+
# @private
|
|
905
|
+
def delete_recorded_exchange_named(name)
|
|
906
|
+
@topology_registry.delete_recorded_exchange_named(name)
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
# @param [Bunny::Channel] ch
|
|
910
|
+
# @param [String] exchange_name
|
|
911
|
+
# @param [String] queue_name
|
|
912
|
+
# @param [String] routing_key
|
|
913
|
+
# @param [Hash] arguments
|
|
914
|
+
# @private
|
|
915
|
+
def record_queue_binding_with(ch, exchange_name, queue_name, routing_key, arguments)
|
|
916
|
+
@topology_registry.record_queue_binding_with(ch, exchange_name, queue_name, routing_key, arguments)
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
# @param [Bunny::Channel] ch
|
|
920
|
+
# @param [String] exchange_name
|
|
921
|
+
# @param [String] queue_name
|
|
922
|
+
# @param [String] routing_key
|
|
923
|
+
# @param [Hash] arguments
|
|
924
|
+
# @private
|
|
925
|
+
def delete_recorded_queue_binding(ch, exchange_name, queue_name, routing_key, arguments)
|
|
926
|
+
@topology_registry.delete_recorded_queue_binding(ch, exchange_name, queue_name, routing_key, arguments)
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# @param [Bunny::Channel] ch
|
|
930
|
+
# @param [String] source_name
|
|
931
|
+
# @param [String] destination_name
|
|
932
|
+
# @param [String] routing_key
|
|
933
|
+
# @param [Hash] arguments
|
|
934
|
+
# @private
|
|
935
|
+
def record_exchange_binding_with(ch, source_name, destination_name, routing_key, arguments)
|
|
936
|
+
@topology_registry.record_exchange_binding_with(ch, source_name, destination_name, routing_key, arguments)
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
# @param [Bunny::Channel] ch
|
|
940
|
+
# @param [String] source_name
|
|
941
|
+
# @param [String] destination_name
|
|
942
|
+
# @param [String] routing_key
|
|
943
|
+
# @param [Hash] arguments
|
|
944
|
+
# @private
|
|
945
|
+
def delete_recorded_exchange_binding(ch, source_name, destination_name, routing_key, arguments)
|
|
946
|
+
@topology_registry.delete_recorded_exchange_binding(ch, source_name, destination_name, routing_key, arguments)
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
# @param [Bunny::Channel] ch
|
|
950
|
+
# @param [String] consumer_tag
|
|
951
|
+
# @param [String] queue_name
|
|
952
|
+
# @param [#call] callable
|
|
953
|
+
# @param [Boolean] manual_ack
|
|
954
|
+
# @param [Boolean] exclusive
|
|
955
|
+
# @param [Hash] arguments
|
|
956
|
+
# @private
|
|
957
|
+
def record_consumer_with(ch, consumer_tag, queue_name, callable, manual_ack, exclusive, arguments)
|
|
958
|
+
@topology_registry.record_consumer_with(ch, consumer_tag, queue_name, callable, manual_ack, exclusive, arguments)
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
# @param [String] consumer_tag
|
|
962
|
+
# @private
|
|
963
|
+
def delete_recorded_consumer(consumer_tag)
|
|
964
|
+
@topology_registry.delete_recorded_consumer(consumer_tag)
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
|
|
770
969
|
# @private
|
|
771
970
|
def announce_network_failure_recovery
|
|
772
971
|
if recovery_attempts_limited?
|
|
@@ -777,18 +976,17 @@ module Bunny
|
|
|
777
976
|
end
|
|
778
977
|
|
|
779
978
|
# @private
|
|
780
|
-
def
|
|
979
|
+
def recover_connection_and_channels
|
|
781
980
|
sleep @network_recovery_interval
|
|
782
981
|
@logger.debug "Will attempt connection recovery..."
|
|
783
982
|
notify_of_recovery_attempt_start
|
|
784
983
|
|
|
785
984
|
self.initialize_transport
|
|
786
985
|
|
|
787
|
-
@logger.
|
|
986
|
+
@logger.debug "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
|
|
788
987
|
self.start
|
|
789
988
|
|
|
790
989
|
if open?
|
|
791
|
-
|
|
792
990
|
@recovering_from_network_failure = false
|
|
793
991
|
@logger.debug "Connection is now open"
|
|
794
992
|
if @reset_recovery_attempt_counter_after_reconnection
|
|
@@ -799,7 +997,6 @@ module Bunny
|
|
|
799
997
|
end
|
|
800
998
|
|
|
801
999
|
recover_channels
|
|
802
|
-
notify_of_recovery_completion
|
|
803
1000
|
end
|
|
804
1001
|
rescue HostListDepleted
|
|
805
1002
|
reset_address_index
|
|
@@ -853,11 +1050,156 @@ module Bunny
|
|
|
853
1050
|
@channel_mutex.synchronize do
|
|
854
1051
|
@channels.each do |n, ch|
|
|
855
1052
|
ch.open
|
|
1053
|
+
ch.recovering!
|
|
856
1054
|
ch.recover_from_network_failure
|
|
857
1055
|
end
|
|
858
1056
|
end
|
|
859
1057
|
end
|
|
860
1058
|
|
|
1059
|
+
# @private
|
|
1060
|
+
def mark_channels_after_recovery!
|
|
1061
|
+
@channel_mutex.synchronize do
|
|
1062
|
+
@channels.each do |n, ch|
|
|
1063
|
+
ch.recovery_completed!
|
|
1064
|
+
end
|
|
1065
|
+
end
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
# @private
|
|
1069
|
+
def recover_topology
|
|
1070
|
+
@logger.debug "Will recover topology now"
|
|
1071
|
+
recover_topology_with
|
|
1072
|
+
end
|
|
1073
|
+
|
|
1074
|
+
# @private
|
|
1075
|
+
def recover_topology_with(filter = nil)
|
|
1076
|
+
exchanges = @topology_registry.filtered_exchanges.reject(&:predeclared?)
|
|
1077
|
+
queues = @topology_registry.filtered_queues
|
|
1078
|
+
queue_bindings = @topology_registry.filtered_queue_bindings
|
|
1079
|
+
exchange_bindings = @topology_registry.filtered_exchange_bindings
|
|
1080
|
+
consumers = @topology_registry.filtered_consumers
|
|
1081
|
+
|
|
1082
|
+
if filter
|
|
1083
|
+
exchanges = exchanges.select(&filter)
|
|
1084
|
+
queues = queues.select(&filter)
|
|
1085
|
+
queue_bindings = queue_bindings.select(&filter)
|
|
1086
|
+
exchange_bindings = exchange_bindings.select(&filter)
|
|
1087
|
+
consumers = consumers.select(&filter)
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
@logger.debug { "Will recover #{exchanges.size} exchange(s)" }
|
|
1091
|
+
exchanges.each do |x|
|
|
1092
|
+
begin
|
|
1093
|
+
recover_exchange(x)
|
|
1094
|
+
rescue Exception => e
|
|
1095
|
+
@logger.error "Caught an exception while recovering exchange #{x.name}: #{e.inspect}"
|
|
1096
|
+
end
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
@logger.debug { "Will recover #{queues.size} queue(s)" }
|
|
1100
|
+
queues.each do |q|
|
|
1101
|
+
begin
|
|
1102
|
+
recover_queue(q)
|
|
1103
|
+
rescue Exception => e
|
|
1104
|
+
@logger.error "Caught an exception while recovering queue #{q.name}: #{e.inspect}"
|
|
1105
|
+
end
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
@logger.debug { "Will recover #{queue_bindings.size + exchange_bindings.size} binding(s)" }
|
|
1109
|
+
queue_bindings.each do |b|
|
|
1110
|
+
begin
|
|
1111
|
+
recover_queue_binding(b)
|
|
1112
|
+
rescue Exception => e
|
|
1113
|
+
@logger.error "Caught an exception while recovering a binding of queue #{b.destination}: #{e.inspect}"
|
|
1114
|
+
end
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
exchange_bindings.each do |b|
|
|
1118
|
+
begin
|
|
1119
|
+
recover_exchange_binding(b)
|
|
1120
|
+
rescue Exception => e
|
|
1121
|
+
@logger.error "Caught an exception while recovering a binding of exchange #{b.source}: #{e.inspect}"
|
|
1122
|
+
end
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
@logger.debug { "Will recover #{consumers.size} consumer(s)" }
|
|
1126
|
+
consumers.each do |c|
|
|
1127
|
+
recover_consumer(c)
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
# @param [Bunny::RecordedExchange] x
|
|
1132
|
+
# @private
|
|
1133
|
+
def recover_exchange(x)
|
|
1134
|
+
opts = {
|
|
1135
|
+
durable: x.durable,
|
|
1136
|
+
auto_delete: x.auto_delete,
|
|
1137
|
+
arguments: x.arguments
|
|
1138
|
+
}
|
|
1139
|
+
x.channel.exchange_declare(x.name, x.type, opts)
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
# @param [Bunny::RecordedQueue] q
|
|
1143
|
+
# @private
|
|
1144
|
+
def recover_queue(q)
|
|
1145
|
+
opts = {
|
|
1146
|
+
durable: q.durable,
|
|
1147
|
+
auto_delete: q.auto_delete,
|
|
1148
|
+
exclusive: q.exclusive,
|
|
1149
|
+
arguments: q.arguments
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
old_name = q.name
|
|
1153
|
+
# this response carries the server-generated name
|
|
1154
|
+
queue_declare_ok = q.channel.queue_declare(q.name_to_use_for_recovery, opts)
|
|
1155
|
+
new_name = queue_declare_ok.queue
|
|
1156
|
+
|
|
1157
|
+
# if the name has changed, update all the bindings where
|
|
1158
|
+
# this queue is the destination, then all consumers
|
|
1159
|
+
if new_name != old_name
|
|
1160
|
+
record_queue_name_change(old_name, new_name)
|
|
1161
|
+
q.channel.record_queue_name_change(old_name, new_name)
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
# @param [String] old_name
|
|
1166
|
+
# @param [String] new_name
|
|
1167
|
+
# @private
|
|
1168
|
+
def record_queue_name_change(old_name, new_name)
|
|
1169
|
+
@topology_registry.record_queue_name_change(old_name, new_name)
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
# @param [Bunny::RecordedQueueBinding] rb
|
|
1173
|
+
# @private
|
|
1174
|
+
def recover_queue_binding(rb)
|
|
1175
|
+
opts = {
|
|
1176
|
+
routing_key: rb.routing_key,
|
|
1177
|
+
arguments: rb.arguments
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
rb.channel.queue_bind_without_recording_topology(rb.destination, rb.source, opts)
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
# @param [Bunny::RecordedExchangeBindingBinding] rb
|
|
1184
|
+
# @private
|
|
1185
|
+
def recover_exchange_binding(rb)
|
|
1186
|
+
opts = {
|
|
1187
|
+
routing_key: rb.routing_key,
|
|
1188
|
+
arguments: rb.arguments
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
rb.channel.exchange_bind_without_recording_topology(rb.source, rb.destination, opts)
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1194
|
+
# @param [Bunny::RecordedConsumer] c
|
|
1195
|
+
# @private
|
|
1196
|
+
def recover_consumer(c)
|
|
1197
|
+
c.channel.maybe_reinitialize_consumer_pool!
|
|
1198
|
+
c.channel.basic_consume(c.queue_name, c.consumer_tag, !c.manual_ack, c.exclusive, c.arguments) do |*args|
|
|
1199
|
+
c.callable.call(*args)
|
|
1200
|
+
end
|
|
1201
|
+
end
|
|
1202
|
+
|
|
861
1203
|
# @private
|
|
862
1204
|
def notify_of_recovery_attempt_start
|
|
863
1205
|
@recovery_attempt_started.call if @recovery_attempt_started
|
|
@@ -1172,7 +1514,8 @@ module Bunny
|
|
|
1172
1514
|
# locking. See a note about "single frame" methods in a comment in `send_frameset`. MK.
|
|
1173
1515
|
channel.synchronize do
|
|
1174
1516
|
if open?
|
|
1175
|
-
frames.
|
|
1517
|
+
data = frames.reduce(+"") { |acc, frame| acc << frame.encode }
|
|
1518
|
+
@transport.write_without_timeout(data)
|
|
1176
1519
|
signal_activity!
|
|
1177
1520
|
else
|
|
1178
1521
|
raise ConnectionClosedError.new(frames)
|
|
@@ -1188,10 +1531,14 @@ module Bunny
|
|
|
1188
1531
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
|
1189
1532
|
# locking. Note that "single frame" methods do not need this kind of synchronization. MK.
|
|
1190
1533
|
channel.synchronize do
|
|
1191
|
-
|
|
1192
|
-
|
|
1534
|
+
if open?
|
|
1535
|
+
@transport.write(data)
|
|
1536
|
+
signal_activity!
|
|
1537
|
+
else
|
|
1538
|
+
raise ConnectionClosedError.new("pre-encoded data (#{data.bytesize} bytes)")
|
|
1539
|
+
end
|
|
1193
1540
|
end
|
|
1194
|
-
end #
|
|
1541
|
+
end # send_raw_without_timeout(data)
|
|
1195
1542
|
|
|
1196
1543
|
# @return [String]
|
|
1197
1544
|
# @api public
|
|
@@ -1359,19 +1706,22 @@ module Bunny
|
|
|
1359
1706
|
|
|
1360
1707
|
# @private
|
|
1361
1708
|
def initialize_transport
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
@
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1709
|
+
address = @addresses[@address_index]
|
|
1710
|
+
if address
|
|
1711
|
+
@transport_mutex.synchronize do
|
|
1712
|
+
@address_index_mutex.synchronize { @address_index += 1 }
|
|
1713
|
+
@transport.close rescue nil # Let's make sure the previous transport socket is closed
|
|
1714
|
+
@transport = Transport.new(self,
|
|
1715
|
+
host_from_address(address),
|
|
1716
|
+
port_from_address(address),
|
|
1717
|
+
@opts.merge(:session_error_handler => @session_error_handler)
|
|
1718
|
+
)
|
|
1719
|
+
|
|
1720
|
+
# Reset the cached progname for the logger only when no logger was provided
|
|
1721
|
+
@default_logger.progname = self.to_s
|
|
1722
|
+
|
|
1723
|
+
@transport
|
|
1724
|
+
end
|
|
1375
1725
|
else
|
|
1376
1726
|
raise HostListDepleted
|
|
1377
1727
|
end
|
|
@@ -1379,7 +1729,9 @@ module Bunny
|
|
|
1379
1729
|
|
|
1380
1730
|
# @private
|
|
1381
1731
|
def maybe_close_transport
|
|
1382
|
-
@
|
|
1732
|
+
@transport_mutex.synchronize do
|
|
1733
|
+
@transport.close if @transport
|
|
1734
|
+
end
|
|
1383
1735
|
end
|
|
1384
1736
|
|
|
1385
1737
|
# Sends AMQ protocol header (also known as preamble).
|
|
@@ -1393,7 +1745,7 @@ module Bunny
|
|
|
1393
1745
|
# @private
|
|
1394
1746
|
def encode_credentials(username, password)
|
|
1395
1747
|
@credentials_encoder.encode_credentials(username, password)
|
|
1396
|
-
end
|
|
1748
|
+
end
|
|
1397
1749
|
|
|
1398
1750
|
# @private
|
|
1399
1751
|
def credentials_encoder_for(mechanism)
|