bunny 2.7.4 → 2.22.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 +5 -5
- data/README.md +61 -35
- data/lib/bunny/channel.rb +186 -50
- data/lib/bunny/channel_id_allocator.rb +3 -1
- data/lib/bunny/consumer.rb +2 -2
- data/lib/bunny/consumer_work_pool.rb +2 -1
- data/lib/bunny/cruby/socket.rb +3 -0
- data/lib/bunny/cruby/ssl_socket.rb +6 -1
- data/lib/bunny/delivery_info.rb +1 -1
- data/lib/bunny/heartbeat_sender.rb +2 -1
- data/lib/bunny/jruby/ssl_socket.rb +5 -0
- data/lib/bunny/queue.rb +36 -8
- data/lib/bunny/reader_loop.rb +22 -10
- data/lib/bunny/session.rb +152 -65
- data/lib/bunny/test_kit.rb +14 -0
- data/lib/bunny/transport.rb +132 -49
- data/lib/bunny/version.rb +1 -1
- data/lib/bunny.rb +45 -4
- metadata +37 -225
- data/.github/ISSUE_TEMPLATE.md +0 -18
- data/.gitignore +0 -28
- data/.rspec +0 -1
- data/.travis.yml +0 -20
- data/.yardopts +0 -8
- data/CONTRIBUTING.md +0 -111
- data/ChangeLog.md +0 -1831
- data/Gemfile +0 -53
- data/LICENSE +0 -21
- data/Rakefile +0 -46
- data/benchmarks/basic_publish/with_128K_messages.rb +0 -35
- data/benchmarks/basic_publish/with_1k_messages.rb +0 -35
- data/benchmarks/basic_publish/with_4K_messages.rb +0 -35
- data/benchmarks/basic_publish/with_64K_messages.rb +0 -35
- data/benchmarks/channel_open.rb +0 -28
- data/benchmarks/mutex_and_monitor.rb +0 -42
- data/benchmarks/queue_declare.rb +0 -29
- data/benchmarks/queue_declare_and_bind.rb +0 -29
- data/benchmarks/queue_declare_bind_and_delete.rb +0 -29
- data/benchmarks/synchronized_sorted_set.rb +0 -53
- data/benchmarks/write_vs_write_nonblock.rb +0 -49
- data/bin/ci/before_build +0 -46
- data/bunny.gemspec +0 -35
- data/docker/Dockerfile +0 -16
- data/docker/docker-entrypoint.sh +0 -37
- data/docker-compose.yml +0 -18
- data/examples/connection/authentication_failure.rb +0 -16
- data/examples/connection/automatic_recovery_with_basic_get.rb +0 -40
- data/examples/connection/automatic_recovery_with_client_named_queues.rb +0 -36
- data/examples/connection/automatic_recovery_with_multiple_consumers.rb +0 -46
- data/examples/connection/automatic_recovery_with_republishing.rb +0 -109
- data/examples/connection/automatic_recovery_with_server_named_queues.rb +0 -35
- data/examples/connection/channel_level_exception.rb +0 -27
- data/examples/connection/disabled_automatic_recovery.rb +0 -34
- data/examples/connection/heartbeat.rb +0 -17
- data/examples/connection/manually_reconnecting_consumer.rb +0 -23
- data/examples/connection/manually_reconnecting_publisher.rb +0 -28
- data/examples/connection/unknown_host.rb +0 -16
- data/examples/consumers/high_and_low_priority.rb +0 -50
- data/examples/guides/exchanges/direct_exchange_routing.rb +0 -36
- data/examples/guides/exchanges/fanout_exchange_routing.rb +0 -28
- data/examples/guides/exchanges/headers_exchange_routing.rb +0 -31
- data/examples/guides/exchanges/mandatory_messages.rb +0 -30
- data/examples/guides/extensions/alternate_exchange.rb +0 -30
- data/examples/guides/extensions/basic_nack.rb +0 -33
- data/examples/guides/extensions/connection_blocked.rb +0 -35
- data/examples/guides/extensions/consumer_cancellation_notification.rb +0 -39
- data/examples/guides/extensions/dead_letter_exchange.rb +0 -32
- data/examples/guides/extensions/exchange_to_exchange_bindings.rb +0 -29
- data/examples/guides/extensions/per_message_ttl.rb +0 -36
- data/examples/guides/extensions/per_queue_message_ttl.rb +0 -36
- data/examples/guides/extensions/publisher_confirms.rb +0 -28
- data/examples/guides/extensions/queue_lease.rb +0 -26
- data/examples/guides/extensions/sender_selected_distribution.rb +0 -32
- data/examples/guides/getting_started/blabbr.rb +0 -27
- data/examples/guides/getting_started/hello_world.rb +0 -22
- data/examples/guides/getting_started/weathr.rb +0 -49
- data/examples/guides/queues/one_off_consumer.rb +0 -25
- data/examples/guides/queues/redeliveries.rb +0 -81
- data/profiling/basic_publish/with_4K_messages.rb +0 -33
- data/repl +0 -3
- data/spec/config/enabled_plugins +0 -1
- data/spec/config/rabbitmq.config +0 -19
- data/spec/higher_level_api/integration/basic_ack_spec.rb +0 -230
- data/spec/higher_level_api/integration/basic_cancel_spec.rb +0 -142
- data/spec/higher_level_api/integration/basic_consume_spec.rb +0 -349
- data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +0 -54
- data/spec/higher_level_api/integration/basic_get_spec.rb +0 -80
- data/spec/higher_level_api/integration/basic_nack_spec.rb +0 -82
- data/spec/higher_level_api/integration/basic_publish_spec.rb +0 -74
- data/spec/higher_level_api/integration/basic_qos_spec.rb +0 -57
- data/spec/higher_level_api/integration/basic_reject_spec.rb +0 -152
- data/spec/higher_level_api/integration/basic_return_spec.rb +0 -33
- data/spec/higher_level_api/integration/channel_close_spec.rb +0 -25
- data/spec/higher_level_api/integration/channel_open_spec.rb +0 -57
- data/spec/higher_level_api/integration/connection_recovery_spec.rb +0 -471
- data/spec/higher_level_api/integration/connection_spec.rb +0 -559
- data/spec/higher_level_api/integration/connection_stop_spec.rb +0 -83
- data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +0 -128
- data/spec/higher_level_api/integration/dead_lettering_spec.rb +0 -75
- data/spec/higher_level_api/integration/exchange_bind_spec.rb +0 -31
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +0 -237
- data/spec/higher_level_api/integration/exchange_delete_spec.rb +0 -105
- data/spec/higher_level_api/integration/exchange_unbind_spec.rb +0 -40
- data/spec/higher_level_api/integration/exclusive_queue_spec.rb +0 -28
- data/spec/higher_level_api/integration/heartbeat_spec.rb +0 -49
- data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
- data/spec/higher_level_api/integration/message_properties_access_spec.rb +0 -95
- data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +0 -24
- data/spec/higher_level_api/integration/publisher_confirms_spec.rb +0 -191
- data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +0 -87
- data/spec/higher_level_api/integration/queue_bind_spec.rb +0 -109
- data/spec/higher_level_api/integration/queue_declare_spec.rb +0 -221
- data/spec/higher_level_api/integration/queue_delete_spec.rb +0 -41
- data/spec/higher_level_api/integration/queue_purge_spec.rb +0 -30
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +0 -54
- data/spec/higher_level_api/integration/read_only_consumer_spec.rb +0 -60
- data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +0 -36
- data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -222
- data/spec/higher_level_api/integration/tx_commit_spec.rb +0 -21
- data/spec/higher_level_api/integration/tx_rollback_spec.rb +0 -21
- data/spec/higher_level_api/integration/with_channel_spec.rb +0 -25
- data/spec/issues/issue100_spec.rb +0 -42
- data/spec/issues/issue141_spec.rb +0 -43
- data/spec/issues/issue202_spec.rb +0 -15
- data/spec/issues/issue224_spec.rb +0 -40
- data/spec/issues/issue465_spec.rb +0 -32
- data/spec/issues/issue78_spec.rb +0 -72
- data/spec/issues/issue83_spec.rb +0 -30
- data/spec/issues/issue97_attachment.json +0 -1
- data/spec/issues/issue97_spec.rb +0 -175
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +0 -83
- data/spec/lower_level_api/integration/basic_consume_spec.rb +0 -99
- data/spec/spec_helper.rb +0 -51
- data/spec/stress/channel_close_stress_spec.rb +0 -64
- data/spec/stress/channel_open_stress_spec.rb +0 -84
- data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +0 -28
- data/spec/stress/concurrent_consumers_stress_spec.rb +0 -71
- data/spec/stress/concurrent_publishers_stress_spec.rb +0 -54
- data/spec/stress/connection_open_close_spec.rb +0 -52
- data/spec/stress/long_running_consumer_spec.rb +0 -84
- data/spec/tls/ca_certificate.pem +0 -29
- data/spec/tls/ca_key.pem +0 -52
- data/spec/tls/client_certificate.pem +0 -29
- data/spec/tls/client_key.pem +0 -51
- data/spec/tls/generate-server-cert.sh +0 -8
- data/spec/tls/server-openssl.cnf +0 -10
- data/spec/tls/server.csr +0 -16
- data/spec/tls/server_certificate.pem +0 -29
- data/spec/tls/server_key.pem +0 -51
- data/spec/unit/bunny_spec.rb +0 -15
- data/spec/unit/concurrent/atomic_fixnum_spec.rb +0 -35
- data/spec/unit/concurrent/condition_spec.rb +0 -82
- data/spec/unit/concurrent/linked_continuation_queue_spec.rb +0 -35
- data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +0 -73
- data/spec/unit/exchange_recovery_spec.rb +0 -39
- data/spec/unit/version_delivery_tag_spec.rb +0 -28
data/lib/bunny/queue.rb
CHANGED
@@ -11,6 +11,23 @@ module Bunny
|
|
11
11
|
# API
|
12
12
|
#
|
13
13
|
|
14
|
+
module Types
|
15
|
+
QUORUM = "quorum"
|
16
|
+
CLASSIC = "classic"
|
17
|
+
STREAM = "stream"
|
18
|
+
|
19
|
+
KNOWN = [CLASSIC, QUORUM, STREAM]
|
20
|
+
|
21
|
+
def self.known?(q_type)
|
22
|
+
KNOWN.include?(q_type)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module XArgs
|
27
|
+
MAX_LENGTH = "x-max-length",
|
28
|
+
QUEUE_TYPE = "x-queue-type"
|
29
|
+
end
|
30
|
+
|
14
31
|
# @return [Bunny::Channel] Channel this queue uses
|
15
32
|
attr_reader :channel
|
16
33
|
# @return [String] Queue name
|
@@ -25,7 +42,8 @@ module Bunny
|
|
25
42
|
# @option opts [Boolean] :durable (false) Should this queue be durable?
|
26
43
|
# @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects?
|
27
44
|
# @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)?
|
28
|
-
# @option opts [
|
45
|
+
# @option opts [String] :type (nil) Type of the declared queue (classic, quorum or stream)
|
46
|
+
# @option opts [Hash] :arguments (nil) Additional optional arguments (typically used by RabbitMQ extensions and plugins)
|
29
47
|
#
|
30
48
|
# @see Bunny::Channel#queue
|
31
49
|
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
@@ -42,7 +60,17 @@ module Bunny
|
|
42
60
|
@exclusive = @options[:exclusive]
|
43
61
|
@server_named = @name.empty?
|
44
62
|
@auto_delete = @options[:auto_delete]
|
45
|
-
@
|
63
|
+
@type = @options[:type]
|
64
|
+
|
65
|
+
@arguments = if @type and !@type.empty? then
|
66
|
+
(@options[:arguments] || {}).merge({XArgs::QUEUE_TYPE => @type})
|
67
|
+
else
|
68
|
+
@options[:arguments]
|
69
|
+
end
|
70
|
+
verify_type!(@arguments)
|
71
|
+
# reassigns updated and verified arguments because Bunny::Channel#declare_queue
|
72
|
+
# accepts a map of options
|
73
|
+
@options[:arguments] = @arguments
|
46
74
|
|
47
75
|
@bindings = Array.new
|
48
76
|
|
@@ -158,7 +186,6 @@ module Bunny
|
|
158
186
|
# @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead
|
159
187
|
# @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements?
|
160
188
|
# @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue?
|
161
|
-
# @option opts [Boolean] :block (false) Should the call block calling thread?
|
162
189
|
# @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin)
|
163
190
|
# @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let Bunny generate it for you.
|
164
191
|
# @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions
|
@@ -373,11 +400,6 @@ module Bunny
|
|
373
400
|
|
374
401
|
protected
|
375
402
|
|
376
|
-
# @private
|
377
|
-
def self.add_default_options(name, opts, block)
|
378
|
-
{ :queue => name, :nowait => (block.nil? && !name.empty?) }.merge(opts)
|
379
|
-
end
|
380
|
-
|
381
403
|
# @private
|
382
404
|
def self.add_default_options(name, opts)
|
383
405
|
# :nowait is always false for Bunny
|
@@ -395,5 +417,11 @@ module Bunny
|
|
395
417
|
h
|
396
418
|
end
|
397
419
|
end
|
420
|
+
|
421
|
+
def verify_type!(args)
|
422
|
+
q_type = (args || {})["x-queue-type"]
|
423
|
+
throw ArgumentError.new(
|
424
|
+
"unsupported queue type #{q_type.inspect}, supported ones: #{Types::KNOWN.join(', ')}") if (q_type and !Types.known?(q_type))
|
425
|
+
end
|
398
426
|
end
|
399
427
|
end
|
data/lib/bunny/reader_loop.rb
CHANGED
@@ -9,13 +9,17 @@ module Bunny
|
|
9
9
|
# @private
|
10
10
|
class ReaderLoop
|
11
11
|
|
12
|
-
def initialize(transport, session,
|
13
|
-
@transport
|
14
|
-
@session
|
15
|
-
@
|
16
|
-
@logger
|
12
|
+
def initialize(transport, session, session_error_handler)
|
13
|
+
@transport = transport
|
14
|
+
@session = session
|
15
|
+
@session_error_handler = session_error_handler
|
16
|
+
@logger = @session.logger
|
17
17
|
|
18
|
-
@mutex
|
18
|
+
@mutex = Mutex.new
|
19
|
+
|
20
|
+
@stopping = false
|
21
|
+
@stopped = false
|
22
|
+
@network_is_down = false
|
19
23
|
end
|
20
24
|
|
21
25
|
|
@@ -33,7 +37,8 @@ module Bunny
|
|
33
37
|
begin
|
34
38
|
break if @mutex.synchronize { @stopping || @stopped || @network_is_down }
|
35
39
|
run_once
|
36
|
-
rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Timeout::Error
|
40
|
+
rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Timeout::Error,
|
41
|
+
OpenSSL::OpenSSLError => e
|
37
42
|
break if terminate? || @session.closing? || @session.closed?
|
38
43
|
|
39
44
|
@network_is_down = true
|
@@ -42,7 +47,7 @@ module Bunny
|
|
42
47
|
@session.handle_network_failure(e)
|
43
48
|
else
|
44
49
|
log_exception(e)
|
45
|
-
@
|
50
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
46
51
|
end
|
47
52
|
rescue ShutdownSignal => _
|
48
53
|
@mutex.synchronize { @stopping = true }
|
@@ -53,7 +58,7 @@ module Bunny
|
|
53
58
|
log_exception(e)
|
54
59
|
|
55
60
|
@network_is_down = true
|
56
|
-
@
|
61
|
+
@session_error_handler.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e))
|
57
62
|
end
|
58
63
|
rescue Errno::EBADF => _ebadf
|
59
64
|
break if terminate?
|
@@ -111,7 +116,14 @@ module Bunny
|
|
111
116
|
end
|
112
117
|
|
113
118
|
def join
|
114
|
-
|
119
|
+
# Thread#join can/would trigger a re-raise of an unhandled exception in this thread.
|
120
|
+
# In addition, Thread.handle_interrupt can be used by other libraries or application code
|
121
|
+
# that would make this join operation fail with an obscure exception.
|
122
|
+
# So we try to save everyone some really unpleasant debugging time by introducing
|
123
|
+
# this condition which typically would not evaluate to true anyway.
|
124
|
+
#
|
125
|
+
# See ruby-amqp/bunny#589 and ruby-amqp/bunny#590 for background.
|
126
|
+
@thread.join if @thread && @thread != Thread.current
|
115
127
|
end
|
116
128
|
|
117
129
|
def kill
|
data/lib/bunny/session.rb
CHANGED
@@ -36,10 +36,10 @@ module Bunny
|
|
36
36
|
DEFAULT_HEARTBEAT = :server
|
37
37
|
# @private
|
38
38
|
DEFAULT_FRAME_MAX = 131072
|
39
|
-
#
|
39
|
+
# Hard limit the user cannot go over regardless of server configuration.
|
40
40
|
# @private
|
41
41
|
CHANNEL_MAX_LIMIT = 65535
|
42
|
-
DEFAULT_CHANNEL_MAX =
|
42
|
+
DEFAULT_CHANNEL_MAX = 2047
|
43
43
|
|
44
44
|
# backwards compatibility
|
45
45
|
# @private
|
@@ -71,6 +71,7 @@ module Bunny
|
|
71
71
|
# Default reconnection interval for TCP connection failures
|
72
72
|
DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
|
73
73
|
|
74
|
+
DEFAULT_RECOVERABLE_EXCEPTIONS = [StandardError, TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError, SystemCallError, Timeout::Error, Bunny::ConnectionLevelException, Bunny::ConnectionClosedError]
|
74
75
|
|
75
76
|
#
|
76
77
|
# API
|
@@ -78,7 +79,7 @@ module Bunny
|
|
78
79
|
|
79
80
|
# @return [Bunny::Transport]
|
80
81
|
attr_reader :transport
|
81
|
-
attr_reader :status, :
|
82
|
+
attr_reader :status, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded
|
82
83
|
attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
|
83
84
|
attr_reader :channel_id_allocator
|
84
85
|
# Authentication mechanism, e.g. "PLAIN" or "EXTERNAL"
|
@@ -89,7 +90,9 @@ module Bunny
|
|
89
90
|
# @return [Integer] Timeout for blocking protocol operations (queue.declare, queue.bind, etc), in milliseconds. Default is 15000.
|
90
91
|
attr_reader :continuation_timeout
|
91
92
|
attr_reader :network_recovery_interval
|
93
|
+
attr_reader :connection_name
|
92
94
|
attr_accessor :socket_configurator
|
95
|
+
attr_accessor :recoverable_exceptions
|
93
96
|
|
94
97
|
# @param [String, Hash] connection_string_or_opts Connection string or a hash of connection options
|
95
98
|
# @param [Hash] optz Extra options not related to connection
|
@@ -101,19 +104,21 @@ module Bunny
|
|
101
104
|
# @option connection_string_or_opts [String] :username ("guest") Username
|
102
105
|
# @option connection_string_or_opts [String] :password ("guest") Password
|
103
106
|
# @option connection_string_or_opts [String] :vhost ("/") Virtual host to use
|
104
|
-
# @option connection_string_or_opts [Integer, Symbol] :heartbeat (:server) Heartbeat
|
107
|
+
# @option connection_string_or_opts [Integer, Symbol] :heartbeat (:server) Heartbeat timeout to offer to the server. :server means use the value suggested by RabbitMQ. 0 means heartbeats and socket read timeouts will be disabled (not recommended).
|
105
108
|
# @option connection_string_or_opts [Integer] :network_recovery_interval (4) Recovery interval periodic network recovery will use. This includes initial pause after network failure.
|
106
109
|
# @option connection_string_or_opts [Boolean] :tls (false) Should TLS/SSL be used?
|
107
110
|
# @option connection_string_or_opts [String] :tls_cert (nil) Path to client TLS/SSL certificate file (.pem)
|
108
111
|
# @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem)
|
109
112
|
# @option connection_string_or_opts [Array<String>] :tls_ca_certificates Array of paths to TLS/SSL CA files (.pem), by default detected from OpenSSL configuration
|
110
113
|
# @option connection_string_or_opts [String] :verify_peer (true) Whether TLS peer verification should be performed
|
111
|
-
# @option connection_string_or_opts [Symbol] :
|
114
|
+
# @option connection_string_or_opts [Symbol] :tls_protocol (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2)
|
115
|
+
# @option connection_string_or_opts [Integer] :channel_max (2047) Maximum number of channels allowed on this connection, minus 1 to account for the special channel 0.
|
112
116
|
# @option connection_string_or_opts [Integer] :continuation_timeout (15000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds.
|
113
117
|
# @option connection_string_or_opts [Integer] :connection_timeout (30) Timeout in seconds for connecting to the server.
|
114
|
-
# @option connection_string_or_opts [Integer] :read_timeout (30) TCP socket read timeout in seconds.
|
118
|
+
# @option connection_string_or_opts [Integer] :read_timeout (30) TCP socket read timeout in seconds. If heartbeats are disabled this will be ignored.
|
115
119
|
# @option connection_string_or_opts [Integer] :write_timeout (30) TCP socket write timeout in seconds.
|
116
|
-
# @option connection_string_or_opts [Proc] :hosts_shuffle_strategy
|
120
|
+
# @option connection_string_or_opts [Proc] :hosts_shuffle_strategy a callable that reorders a list of host strings, defaults to Array#shuffle
|
121
|
+
# @option connection_string_or_opts [Proc] :recovery_completed a callable that will be called when a network recovery is performed
|
117
122
|
# @option connection_string_or_opts [Logger] :logger The logger. If missing, one is created using :log_file and :log_level.
|
118
123
|
# @option connection_string_or_opts [IO, String] :log_file The file or path to use when creating a logger. Defaults to STDOUT.
|
119
124
|
# @option connection_string_or_opts [IO, String] :logfile DEPRECATED: use :log_file instead. The file or path to use when creating a logger. Defaults to STDOUT.
|
@@ -121,10 +126,15 @@ module Bunny
|
|
121
126
|
# @option connection_string_or_opts [Boolean] :automatically_recover (true) Should automatically recover from network failures?
|
122
127
|
# @option connection_string_or_opts [Integer] :recovery_attempts (nil) Max number of recovery attempts, nil means forever
|
123
128
|
# @option connection_string_or_opts [Integer] :reset_recovery_attempts_after_reconnection (true) Should recovery attempt counter be reset after successful reconnection? When set to false, the attempt counter will last through the entire lifetime of the connection object.
|
129
|
+
# @option connection_string_or_opts [Proc] :recovery_attempt_started (nil) Will be called before every connection recovery attempt
|
130
|
+
# @option connection_string_or_opts [Proc] :recovery_completed (nil) Will be called after successful connection recovery
|
131
|
+
# @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
|
124
132
|
# @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.
|
125
134
|
#
|
126
135
|
# @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL
|
127
136
|
# @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use
|
137
|
+
# @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.
|
128
138
|
#
|
129
139
|
# @see http://rubybunny.info/articles/connecting.html Connecting to RabbitMQ guide
|
130
140
|
# @see http://rubybunny.info/articles/tls.html TLS/SSL guide
|
@@ -151,22 +161,25 @@ module Bunny
|
|
151
161
|
@addresses = self.addresses_from(opts)
|
152
162
|
@address_index = 0
|
153
163
|
|
154
|
-
|
155
|
-
@logger = opts.fetch(:logger, init_default_logger(log_file, log_level))
|
156
|
-
|
164
|
+
@transport = nil
|
157
165
|
@user = self.username_from(opts)
|
158
166
|
@pass = self.password_from(opts)
|
159
167
|
@vhost = self.vhost_from(opts)
|
160
168
|
@threaded = opts.fetch(:threaded, true)
|
161
169
|
|
170
|
+
# re-init, see above
|
171
|
+
@logger = opts.fetch(:logger, init_default_logger(log_file, log_level))
|
172
|
+
|
162
173
|
validate_connection_options(opts)
|
174
|
+
@last_connection_error = nil
|
163
175
|
|
164
176
|
# should automatic recovery from network failures be used?
|
165
177
|
@automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil?
|
166
178
|
true
|
167
179
|
else
|
168
|
-
opts[:automatically_recover]
|
180
|
+
opts[:automatically_recover] | opts[:automatic_recovery]
|
169
181
|
end
|
182
|
+
@recovering_from_network_failure = false
|
170
183
|
@max_recovery_attempts = opts[:recovery_attempts]
|
171
184
|
@recovery_attempts = @max_recovery_attempts
|
172
185
|
# When this is set, connection attempts won't be reset after
|
@@ -180,6 +193,7 @@ module Bunny
|
|
180
193
|
@continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT)
|
181
194
|
|
182
195
|
@status = :not_connected
|
196
|
+
@manually_closed = false
|
183
197
|
@blocked = false
|
184
198
|
|
185
199
|
# these are negotiated with the broker during the connection tuning phase
|
@@ -187,10 +201,13 @@ module Bunny
|
|
187
201
|
@client_channel_max = normalize_client_channel_max(opts.fetch(:channel_max, DEFAULT_CHANNEL_MAX))
|
188
202
|
# will be-renegotiated during connection tuning steps. MK.
|
189
203
|
@channel_max = @client_channel_max
|
204
|
+
@heartbeat_sender = nil
|
190
205
|
@client_heartbeat = self.heartbeat_from(opts)
|
191
206
|
|
192
207
|
client_props = opts[:properties] || opts[:client_properties] || {}
|
208
|
+
@connection_name = client_props[:connection_name] || opts[:connection_name]
|
193
209
|
@client_properties = DEFAULT_CLIENT_PROPERTIES.merge(client_props)
|
210
|
+
.merge(connection_name: connection_name)
|
194
211
|
@mechanism = normalize_auth_mechanism(opts.fetch(:auth_mechanism, "PLAIN"))
|
195
212
|
@credentials_encoder = credentials_encoder_for(@mechanism)
|
196
213
|
@locale = @opts.fetch(:locale, DEFAULT_LOCALE)
|
@@ -207,10 +224,17 @@ module Bunny
|
|
207
224
|
|
208
225
|
@channels = Hash.new
|
209
226
|
|
210
|
-
@
|
227
|
+
@recovery_attempt_started = opts[:recovery_attempt_started]
|
228
|
+
@recovery_completed = opts[:recovery_completed]
|
229
|
+
@recovery_attempts_exhausted = opts[:recovery_attempts_exhausted]
|
230
|
+
|
231
|
+
@session_error_handler = opts.fetch(:session_error_handler, Thread.current)
|
232
|
+
|
233
|
+
@recoverable_exceptions = DEFAULT_RECOVERABLE_EXCEPTIONS.dup
|
211
234
|
|
212
235
|
self.reset_continuations
|
213
236
|
self.initialize_transport
|
237
|
+
|
214
238
|
end
|
215
239
|
|
216
240
|
def validate_connection_options(options)
|
@@ -232,9 +256,13 @@ module Bunny
|
|
232
256
|
# @return [String] Virtual host used
|
233
257
|
def virtual_host; self.vhost; end
|
234
258
|
|
235
|
-
# @
|
259
|
+
# @deprecated
|
260
|
+
# @return [Integer] Heartbeat timeout (not interval) used
|
236
261
|
def heartbeat_interval; self.heartbeat; end
|
237
262
|
|
263
|
+
# @return [Integer] Heartbeat timeout used
|
264
|
+
def heartbeat_timeout; self.heartbeat; end
|
265
|
+
|
238
266
|
# @return [Boolean] true if this connection uses TLS (SSL)
|
239
267
|
def uses_tls?
|
240
268
|
@transport.uses_tls?
|
@@ -303,10 +331,6 @@ module Bunny
|
|
303
331
|
@transport.post_initialize_socket
|
304
332
|
@transport.connect
|
305
333
|
|
306
|
-
if @socket_configurator
|
307
|
-
@transport.configure_socket(&@socket_configurator)
|
308
|
-
end
|
309
|
-
|
310
334
|
self.init_connection
|
311
335
|
self.open_connection
|
312
336
|
|
@@ -333,6 +357,14 @@ module Bunny
|
|
333
357
|
self
|
334
358
|
end
|
335
359
|
|
360
|
+
def update_secret(value, reason)
|
361
|
+
@transport.send_frame(AMQ::Protocol::Connection::UpdateSecret.encode(value, reason))
|
362
|
+
@last_update_secret_ok = wait_on_continuations
|
363
|
+
raise_if_continuation_resulted_in_a_connection_error!
|
364
|
+
|
365
|
+
@last_update_secret_ok
|
366
|
+
end
|
367
|
+
|
336
368
|
# Socket operation write timeout used by this connection
|
337
369
|
# @return [Integer]
|
338
370
|
# @private
|
@@ -363,14 +395,16 @@ module Bunny
|
|
363
395
|
alias channel create_channel
|
364
396
|
|
365
397
|
# Closes the connection. This involves closing all of its channels.
|
366
|
-
def close
|
398
|
+
def close(await_response = true)
|
367
399
|
@status_mutex.synchronize { @status = :closing }
|
368
400
|
|
369
401
|
ignoring_io_errors do
|
370
402
|
if @transport.open?
|
403
|
+
@logger.debug "Transport is still open..."
|
371
404
|
close_all_channels
|
372
405
|
|
373
|
-
|
406
|
+
@logger.debug "Will close all channels...."
|
407
|
+
self.close_connection(await_response)
|
374
408
|
end
|
375
409
|
|
376
410
|
clean_up_on_shutdown
|
@@ -379,6 +413,8 @@ module Bunny
|
|
379
413
|
@status = :closed
|
380
414
|
@manually_closed = true
|
381
415
|
end
|
416
|
+
@logger.debug "Connection is closed"
|
417
|
+
true
|
382
418
|
end
|
383
419
|
alias stop close
|
384
420
|
|
@@ -413,7 +449,7 @@ module Bunny
|
|
413
449
|
@status_mutex.synchronize { @status == :closed }
|
414
450
|
end
|
415
451
|
|
416
|
-
# @return [Boolean] true if this AMQP 0.9.1 connection has been
|
452
|
+
# @return [Boolean] true if this AMQP 0.9.1 connection has been closed by the user (as opposed to the server)
|
417
453
|
def manually_closed?
|
418
454
|
@status_mutex.synchronize { @manually_closed == true }
|
419
455
|
end
|
@@ -464,7 +500,7 @@ module Bunny
|
|
464
500
|
# @param [String] uri amqp or amqps URI to parse
|
465
501
|
# @return [Hash] Parsed URI as a hash
|
466
502
|
def self.parse_uri(uri)
|
467
|
-
AMQ::Settings.
|
503
|
+
AMQ::Settings.configure(uri)
|
468
504
|
end
|
469
505
|
|
470
506
|
# Checks if a queue with given name exists.
|
@@ -507,6 +543,24 @@ module Bunny
|
|
507
543
|
end
|
508
544
|
end
|
509
545
|
|
546
|
+
# Defines a callable (e.g. a block) that will be called
|
547
|
+
# before every connection recovery attempt.
|
548
|
+
def before_recovery_attempt_starts(&block)
|
549
|
+
@recovery_attempt_started = block
|
550
|
+
end
|
551
|
+
|
552
|
+
# Defines a callable (e.g. a block) that will be called
|
553
|
+
# after successful connection recovery.
|
554
|
+
def after_recovery_completed(&block)
|
555
|
+
@recovery_completed = block
|
556
|
+
end
|
557
|
+
|
558
|
+
# Defines a callable (e.g. a block) that will be called
|
559
|
+
# when the connection recovery failed after the specified
|
560
|
+
# numbers of recovery attempts.
|
561
|
+
def after_recovery_attempts_exhausted(&block)
|
562
|
+
@recovery_attempts_exhausted = block
|
563
|
+
end
|
510
564
|
|
511
565
|
#
|
512
566
|
# Implementation
|
@@ -563,11 +617,13 @@ module Bunny
|
|
563
617
|
end
|
564
618
|
|
565
619
|
# @private
|
566
|
-
def close_connection(
|
620
|
+
def close_connection(await_response = true)
|
567
621
|
if @transport.open?
|
622
|
+
@logger.debug "Transport is still open"
|
568
623
|
@transport.send_frame(AMQ::Protocol::Connection::Close.encode(200, "Goodbye", 0, 0))
|
569
624
|
|
570
|
-
if
|
625
|
+
if await_response
|
626
|
+
@logger.debug "Waiting for a connection.close-ok..."
|
571
627
|
@last_connection_close_ok = wait_on_continuations
|
572
628
|
end
|
573
629
|
end
|
@@ -617,6 +673,8 @@ module Bunny
|
|
617
673
|
when AMQ::Protocol::Connection::Unblocked then
|
618
674
|
@blocked = false
|
619
675
|
@unblock_callback.call(method) if @unblock_callback
|
676
|
+
when AMQ::Protocol::Connection::UpdateSecretOk then
|
677
|
+
@continuations.push(method)
|
620
678
|
when AMQ::Protocol::Channel::Close then
|
621
679
|
begin
|
622
680
|
ch = synchronised_find_channel(ch_number)
|
@@ -625,8 +683,12 @@ module Bunny
|
|
625
683
|
# avoid doing that while holding a mutex lock. MK.
|
626
684
|
ch.handle_method(method)
|
627
685
|
ensure
|
628
|
-
|
629
|
-
|
686
|
+
if ch.nil?
|
687
|
+
@logger.warn "Received a server-sent channel.close but the channel was not found locally. Ignoring the frame."
|
688
|
+
else
|
689
|
+
# synchronises on @channel_mutex under the hood
|
690
|
+
self.unregister_channel(ch)
|
691
|
+
end
|
630
692
|
end
|
631
693
|
when AMQ::Protocol::Basic::GetEmpty then
|
632
694
|
ch = find_channel(ch_number)
|
@@ -697,9 +759,7 @@ module Bunny
|
|
697
759
|
|
698
760
|
# @private
|
699
761
|
def recoverable_network_failure?(exception)
|
700
|
-
|
701
|
-
# So just recover unconditionally. MK.
|
702
|
-
true
|
762
|
+
@recoverable_exceptions.any? {|x| exception.kind_of? x}
|
703
763
|
end
|
704
764
|
|
705
765
|
# @private
|
@@ -720,6 +780,7 @@ module Bunny
|
|
720
780
|
def recover_from_network_failure
|
721
781
|
sleep @network_recovery_interval
|
722
782
|
@logger.debug "Will attempt connection recovery..."
|
783
|
+
notify_of_recovery_attempt_start
|
723
784
|
|
724
785
|
self.initialize_transport
|
725
786
|
|
@@ -738,20 +799,28 @@ module Bunny
|
|
738
799
|
end
|
739
800
|
|
740
801
|
recover_channels
|
802
|
+
notify_of_recovery_completion
|
741
803
|
end
|
742
804
|
rescue HostListDepleted
|
743
805
|
reset_address_index
|
744
806
|
retry
|
745
|
-
rescue
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
807
|
+
rescue => e
|
808
|
+
if recoverable_network_failure?(e)
|
809
|
+
@logger.warn "TCP connection failed"
|
810
|
+
if should_retry_recovery?
|
811
|
+
@logger.warn "Reconnecting in #{@network_recovery_interval} seconds"
|
812
|
+
decrement_recovery_attemp_counter!
|
750
813
|
announce_network_failure_recovery
|
751
814
|
retry
|
815
|
+
else
|
816
|
+
@logger.error "Ran out of recovery attempts (limit set to #{@max_recovery_attempts}), giving up"
|
817
|
+
@transport.close
|
818
|
+
self.close(false)
|
819
|
+
@manually_closed = false
|
820
|
+
notify_of_recovery_attempts_exhausted
|
752
821
|
end
|
753
822
|
else
|
754
|
-
|
823
|
+
raise e
|
755
824
|
end
|
756
825
|
end
|
757
826
|
|
@@ -767,8 +836,10 @@ module Bunny
|
|
767
836
|
|
768
837
|
# @private
|
769
838
|
def decrement_recovery_attemp_counter!
|
770
|
-
|
771
|
-
|
839
|
+
if @recovery_attempts
|
840
|
+
@recovery_attempts -= 1
|
841
|
+
@logger.debug "#{@recovery_attempts} recovery attempts left"
|
842
|
+
end
|
772
843
|
@recovery_attempts
|
773
844
|
end
|
774
845
|
|
@@ -782,12 +853,26 @@ module Bunny
|
|
782
853
|
@channel_mutex.synchronize do
|
783
854
|
@channels.each do |n, ch|
|
784
855
|
ch.open
|
785
|
-
|
786
856
|
ch.recover_from_network_failure
|
787
857
|
end
|
788
858
|
end
|
789
859
|
end
|
790
860
|
|
861
|
+
# @private
|
862
|
+
def notify_of_recovery_attempt_start
|
863
|
+
@recovery_attempt_started.call if @recovery_attempt_started
|
864
|
+
end
|
865
|
+
|
866
|
+
# @private
|
867
|
+
def notify_of_recovery_completion
|
868
|
+
@recovery_completed.call if @recovery_completed
|
869
|
+
end
|
870
|
+
|
871
|
+
# @private
|
872
|
+
def notify_of_recovery_attempts_exhausted
|
873
|
+
@recovery_attempts_exhausted.call if @recovery_attempts_exhausted
|
874
|
+
end
|
875
|
+
|
791
876
|
# @private
|
792
877
|
def instantiate_connection_level_exception(frame)
|
793
878
|
case frame
|
@@ -823,7 +908,7 @@ module Bunny
|
|
823
908
|
|
824
909
|
clean_up_on_shutdown
|
825
910
|
if threaded?
|
826
|
-
@
|
911
|
+
@session_error_handler.raise(@last_connection_error)
|
827
912
|
else
|
828
913
|
raise @last_connection_error
|
829
914
|
end
|
@@ -943,7 +1028,7 @@ module Bunny
|
|
943
1028
|
|
944
1029
|
# @private
|
945
1030
|
def heartbeat_from(options)
|
946
|
-
options[:heartbeat] || options[:
|
1031
|
+
options[:heartbeat] || options[:heartbeat_timeout] || options[:requested_heartbeat] || options[:heartbeat_interval] || DEFAULT_HEARTBEAT
|
947
1032
|
end
|
948
1033
|
|
949
1034
|
# @private
|
@@ -980,7 +1065,7 @@ module Bunny
|
|
980
1065
|
|
981
1066
|
# @private
|
982
1067
|
def reader_loop
|
983
|
-
@reader_loop ||= ReaderLoop.new(@transport, self,
|
1068
|
+
@reader_loop ||= ReaderLoop.new(@transport, self, @session_error_handler)
|
984
1069
|
end
|
985
1070
|
|
986
1071
|
# @private
|
@@ -1068,12 +1153,18 @@ module Bunny
|
|
1068
1153
|
# threads publish on the same channel aggressively, at some point frames will be
|
1069
1154
|
# delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
|
1070
1155
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
1071
|
-
# locking. Note that "single frame" methods do not need this kind of synchronization
|
1156
|
+
# locking. Note that "single frame" methods technically do not need this kind of synchronization
|
1157
|
+
# (no incorrect frame interleaving of the same kind as with basic.publish isn't possible) but we
|
1158
|
+
# still recommend not sharing channels between threads except for consumer-only cases in the docs. MK.
|
1072
1159
|
channel.synchronize do
|
1073
1160
|
# see rabbitmq/rabbitmq-server#156
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1161
|
+
if open?
|
1162
|
+
data = frames.reduce("") { |acc, frame| acc << frame.encode }
|
1163
|
+
@transport.write(data)
|
1164
|
+
signal_activity!
|
1165
|
+
else
|
1166
|
+
raise ConnectionClosedError.new(frames)
|
1167
|
+
end
|
1077
1168
|
end
|
1078
1169
|
end # send_frameset(frames)
|
1079
1170
|
|
@@ -1087,10 +1178,14 @@ module Bunny
|
|
1087
1178
|
# threads publish on the same channel aggressively, at some point frames will be
|
1088
1179
|
# delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
|
1089
1180
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
1090
|
-
# locking.
|
1181
|
+
# locking. See a note about "single frame" methods in a comment in `send_frameset`. MK.
|
1091
1182
|
channel.synchronize do
|
1092
|
-
|
1093
|
-
|
1183
|
+
if open?
|
1184
|
+
frames.each { |frame| self.send_frame_without_timeout(frame, false) }
|
1185
|
+
signal_activity!
|
1186
|
+
else
|
1187
|
+
raise ConnectionClosedError.new(frames)
|
1188
|
+
end
|
1094
1189
|
end
|
1095
1190
|
end # send_frameset_without_timeout(frames)
|
1096
1191
|
|
@@ -1177,26 +1272,18 @@ module Bunny
|
|
1177
1272
|
@logger.debug { "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}" }
|
1178
1273
|
@logger.info "Heartbeat interval used (in seconds): #{@heartbeat}"
|
1179
1274
|
|
1180
|
-
# We set the read_write_timeout to twice the heartbeat value
|
1275
|
+
# We set the read_write_timeout to twice the heartbeat value,
|
1276
|
+
# and then some padding for edge cases.
|
1181
1277
|
# This allows us to miss a single heartbeat before we time out the socket.
|
1182
|
-
#
|
1183
|
-
#
|
1184
|
-
|
1185
|
-
|
1186
|
-
@transport.read_timeout = if heartbeat_disabled?(@client_heartbeat) || heartbeat_disabled?(@heartbeat)
|
1187
|
-
@logger.debug { "Will use default socket read timeout of #{Transport::DEFAULT_READ_TIMEOUT}" }
|
1188
|
-
Transport::DEFAULT_READ_TIMEOUT
|
1189
|
-
else
|
1190
|
-
# pad to account for edge cases. MK.
|
1191
|
-
n = @heartbeat * 2.2
|
1192
|
-
@logger.debug { "Will use socket read timeout of #{n}" }
|
1193
|
-
n
|
1194
|
-
end
|
1195
|
-
|
1278
|
+
# If heartbeats are disabled, assume that TCP keepalives or a similar mechanism will be used
|
1279
|
+
# and disable socket read timeouts. See ruby-amqp/bunny#551.
|
1280
|
+
@transport.read_timeout = @heartbeat * 2.2
|
1281
|
+
@logger.debug { "Will use socket read timeout of #{@transport.read_timeout.to_i} seconds" }
|
1196
1282
|
|
1197
1283
|
# if there are existing channels we've just recovered from
|
1198
1284
|
# a network failure and need to fix the allocated set. See issue 205. MK.
|
1199
1285
|
if @channels.empty?
|
1286
|
+
@logger.debug { "Initializing channel ID allocator with channel_max = #{@channel_max}" }
|
1200
1287
|
@channel_id_allocator = ChannelIdAllocator.new(@channel_max)
|
1201
1288
|
end
|
1202
1289
|
|
@@ -1241,7 +1328,7 @@ module Bunny
|
|
1241
1328
|
end
|
1242
1329
|
|
1243
1330
|
if threaded?
|
1244
|
-
@
|
1331
|
+
@session_error_handler.raise(e)
|
1245
1332
|
else
|
1246
1333
|
raise e
|
1247
1334
|
end
|
@@ -1287,8 +1374,8 @@ module Bunny
|
|
1287
1374
|
@transport = Transport.new(self,
|
1288
1375
|
host_from_address(address),
|
1289
1376
|
port_from_address(address),
|
1290
|
-
@opts.merge(:
|
1291
|
-
|
1377
|
+
@opts.merge(:session_error_handler => @session_error_handler)
|
1378
|
+
)
|
1292
1379
|
|
1293
1380
|
# Reset the cached progname for the logger only when no logger was provided
|
1294
1381
|
@default_logger.progname = self.to_s
|