bunny 1.3.0 → 2.17.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/.github/ISSUE_TEMPLATE.md +18 -0
- data/.gitignore +7 -1
- data/.rspec +1 -3
- data/.travis.yml +21 -14
- data/CONTRIBUTING.md +132 -0
- data/ChangeLog.md +887 -1
- data/Gemfile +13 -13
- data/LICENSE +1 -1
- data/README.md +46 -60
- data/Rakefile +54 -0
- data/bunny.gemspec +5 -11
- data/docker-compose.yml +28 -0
- data/docker/Dockerfile +24 -0
- data/docker/apt/preferences.d/erlang +3 -0
- data/docker/apt/sources.list.d/bintray.rabbitmq.list +2 -0
- data/docker/docker-entrypoint.sh +26 -0
- data/docker/rabbitmq.conf +29 -0
- data/examples/connection/automatic_recovery_with_basic_get.rb +1 -1
- data/examples/connection/automatic_recovery_with_client_named_queues.rb +1 -1
- data/examples/connection/automatic_recovery_with_multiple_consumers.rb +1 -1
- data/examples/connection/automatic_recovery_with_republishing.rb +1 -1
- data/examples/connection/automatic_recovery_with_server_named_queues.rb +1 -1
- data/examples/connection/channel_level_exception.rb +1 -9
- data/examples/connection/disabled_automatic_recovery.rb +1 -1
- data/examples/connection/heartbeat.rb +1 -1
- data/examples/consumers/high_and_low_priority.rb +1 -1
- data/examples/guides/extensions/alternate_exchange.rb +2 -0
- data/examples/guides/extensions/basic_nack.rb +1 -1
- data/examples/guides/extensions/dead_letter_exchange.rb +1 -1
- data/examples/guides/getting_started/hello_world.rb +2 -0
- data/examples/guides/getting_started/weathr.rb +2 -0
- data/examples/guides/queues/one_off_consumer.rb +2 -0
- data/examples/guides/queues/redeliveries.rb +4 -2
- data/lib/bunny.rb +8 -4
- data/lib/bunny/channel.rb +268 -153
- data/lib/bunny/channel_id_allocator.rb +6 -4
- data/lib/bunny/concurrent/continuation_queue.rb +34 -13
- data/lib/bunny/consumer_work_pool.rb +34 -6
- data/lib/bunny/cruby/socket.rb +48 -21
- data/lib/bunny/cruby/ssl_socket.rb +65 -4
- data/lib/bunny/exceptions.rb +25 -4
- data/lib/bunny/exchange.rb +24 -28
- data/lib/bunny/get_response.rb +1 -1
- data/lib/bunny/heartbeat_sender.rb +3 -2
- data/lib/bunny/jruby/socket.rb +23 -6
- data/lib/bunny/jruby/ssl_socket.rb +5 -0
- data/lib/bunny/queue.rb +31 -22
- data/lib/bunny/reader_loop.rb +31 -18
- data/lib/bunny/session.rb +448 -159
- data/lib/bunny/test_kit.rb +14 -0
- data/lib/bunny/timeout.rb +1 -12
- data/lib/bunny/transport.rb +205 -98
- data/lib/bunny/version.rb +1 -1
- data/repl +1 -1
- data/spec/config/enabled_plugins +1 -0
- data/spec/config/rabbitmq.conf +13 -0
- data/spec/higher_level_api/integration/basic_ack_spec.rb +175 -16
- data/spec/higher_level_api/integration/basic_cancel_spec.rb +77 -11
- data/spec/higher_level_api/integration/basic_consume_spec.rb +60 -55
- data/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb +6 -6
- data/spec/higher_level_api/integration/basic_get_spec.rb +31 -7
- data/spec/higher_level_api/integration/basic_nack_spec.rb +22 -19
- data/spec/higher_level_api/integration/basic_publish_spec.rb +11 -100
- data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -4
- data/spec/higher_level_api/integration/basic_reject_spec.rb +94 -16
- data/spec/higher_level_api/integration/basic_return_spec.rb +4 -4
- data/spec/higher_level_api/integration/channel_close_spec.rb +51 -10
- data/spec/higher_level_api/integration/channel_open_spec.rb +12 -12
- data/spec/higher_level_api/integration/connection_recovery_spec.rb +424 -221
- data/spec/higher_level_api/integration/connection_spec.rb +300 -126
- data/spec/higher_level_api/integration/connection_stop_spec.rb +31 -19
- data/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb +17 -17
- data/spec/higher_level_api/integration/dead_lettering_spec.rb +34 -11
- data/spec/higher_level_api/integration/exchange_bind_spec.rb +5 -5
- data/spec/higher_level_api/integration/exchange_declare_spec.rb +32 -31
- data/spec/higher_level_api/integration/exchange_delete_spec.rb +12 -12
- data/spec/higher_level_api/integration/exchange_unbind_spec.rb +5 -5
- data/spec/higher_level_api/integration/exclusive_queue_spec.rb +5 -5
- data/spec/higher_level_api/integration/heartbeat_spec.rb +26 -8
- data/spec/higher_level_api/integration/message_properties_access_spec.rb +49 -49
- data/spec/higher_level_api/integration/predeclared_exchanges_spec.rb +2 -2
- data/spec/higher_level_api/integration/publisher_confirms_spec.rb +156 -42
- data/spec/higher_level_api/integration/publishing_edge_cases_spec.rb +19 -19
- data/spec/higher_level_api/integration/queue_bind_spec.rb +23 -23
- data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -34
- data/spec/higher_level_api/integration/queue_delete_spec.rb +2 -2
- data/spec/higher_level_api/integration/queue_purge_spec.rb +5 -5
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +6 -6
- data/spec/higher_level_api/integration/read_only_consumer_spec.rb +9 -9
- data/spec/higher_level_api/integration/sender_selected_distribution_spec.rb +10 -10
- data/spec/higher_level_api/integration/tls_connection_spec.rb +224 -89
- data/spec/higher_level_api/integration/toxiproxy_spec.rb +76 -0
- data/spec/higher_level_api/integration/tx_commit_spec.rb +1 -1
- data/spec/higher_level_api/integration/tx_rollback_spec.rb +1 -1
- data/spec/higher_level_api/integration/with_channel_spec.rb +2 -2
- data/spec/issues/issue100_spec.rb +11 -11
- data/spec/issues/issue141_spec.rb +13 -14
- data/spec/issues/issue202_spec.rb +1 -1
- data/spec/issues/issue224_spec.rb +40 -0
- data/spec/issues/issue465_spec.rb +32 -0
- data/spec/issues/issue549_spec.rb +30 -0
- data/spec/issues/issue78_spec.rb +21 -24
- data/spec/issues/issue83_spec.rb +5 -6
- data/spec/issues/issue97_spec.rb +44 -45
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +15 -16
- data/spec/lower_level_api/integration/basic_consume_spec.rb +20 -21
- data/spec/spec_helper.rb +8 -26
- data/spec/stress/channel_close_stress_spec.rb +64 -0
- data/spec/stress/channel_open_stress_spec.rb +15 -9
- data/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb +7 -7
- data/spec/stress/concurrent_consumers_stress_spec.rb +18 -16
- data/spec/stress/concurrent_publishers_stress_spec.rb +16 -19
- data/spec/stress/connection_open_close_spec.rb +9 -9
- data/spec/stress/merry_go_round_spec.rb +105 -0
- data/spec/tls/client_key.pem +49 -25
- data/spec/tls/generate-server-cert.sh +8 -0
- data/spec/tls/server-openssl.cnf +10 -0
- data/spec/tls/server.csr +16 -0
- data/spec/tls/server_key.pem +49 -25
- data/spec/toxiproxy_helper.rb +28 -0
- data/spec/unit/bunny_spec.rb +5 -5
- data/spec/unit/concurrent/atomic_fixnum_spec.rb +6 -6
- data/spec/unit/concurrent/condition_spec.rb +8 -8
- data/spec/unit/concurrent/linked_continuation_queue_spec.rb +2 -2
- data/spec/unit/concurrent/synchronized_sorted_set_spec.rb +16 -16
- data/spec/unit/exchange_recovery_spec.rb +39 -0
- data/spec/unit/version_delivery_tag_spec.rb +3 -3
- metadata +65 -47
- data/.ruby-version +0 -1
- data/lib/bunny/compatibility.rb +0 -24
- data/lib/bunny/system_timer.rb +0 -20
- data/spec/compatibility/queue_declare_spec.rb +0 -44
- data/spec/compatibility/queue_declare_with_default_channel_spec.rb +0 -33
- data/spec/higher_level_api/integration/basic_recover_spec.rb +0 -18
- data/spec/higher_level_api/integration/confirm_select_spec.rb +0 -19
- data/spec/higher_level_api/integration/consistent_hash_exchange_spec.rb +0 -50
- data/spec/higher_level_api/integration/merry_go_round_spec.rb +0 -85
- data/spec/stress/long_running_consumer_spec.rb +0 -83
- data/spec/tls/cacert.pem +0 -18
- data/spec/tls/client_cert.pem +0 -18
- data/spec/tls/server_cert.pem +0 -18
- data/spec/unit/system_timer_spec.rb +0 -10
data/lib/bunny/session.rb
CHANGED
@@ -36,21 +36,17 @@ 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
|
46
46
|
CONNECT_TIMEOUT = Transport::DEFAULT_CONNECTION_TIMEOUT
|
47
47
|
|
48
48
|
# @private
|
49
|
-
DEFAULT_CONTINUATION_TIMEOUT =
|
50
|
-
8000
|
51
|
-
else
|
52
|
-
4000
|
53
|
-
end
|
49
|
+
DEFAULT_CONTINUATION_TIMEOUT = 15000
|
54
50
|
|
55
51
|
# RabbitMQ client metadata
|
56
52
|
DEFAULT_CLIENT_PROPERTIES = {
|
@@ -82,62 +78,95 @@ module Bunny
|
|
82
78
|
|
83
79
|
# @return [Bunny::Transport]
|
84
80
|
attr_reader :transport
|
85
|
-
attr_reader :status, :
|
81
|
+
attr_reader :status, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded
|
86
82
|
attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
|
87
|
-
attr_reader :default_channel
|
88
83
|
attr_reader :channel_id_allocator
|
89
84
|
# Authentication mechanism, e.g. "PLAIN" or "EXTERNAL"
|
90
85
|
# @return [String]
|
91
86
|
attr_reader :mechanism
|
92
87
|
# @return [Logger]
|
93
88
|
attr_reader :logger
|
94
|
-
# @return [Integer] Timeout for blocking protocol operations (queue.declare, queue.bind, etc), in milliseconds. Default is
|
89
|
+
# @return [Integer] Timeout for blocking protocol operations (queue.declare, queue.bind, etc), in milliseconds. Default is 15000.
|
95
90
|
attr_reader :continuation_timeout
|
96
|
-
|
91
|
+
attr_reader :network_recovery_interval
|
92
|
+
attr_reader :connection_name
|
93
|
+
attr_accessor :socket_configurator
|
97
94
|
|
98
95
|
# @param [String, Hash] connection_string_or_opts Connection string or a hash of connection options
|
99
96
|
# @param [Hash] optz Extra options not related to connection
|
100
97
|
#
|
101
98
|
# @option connection_string_or_opts [String] :host ("127.0.0.1") Hostname or IP address to connect to
|
99
|
+
# @option connection_string_or_opts [Array<String>] :hosts (["127.0.0.1"]) list of hostname or IP addresses to select hostname from when connecting
|
100
|
+
# @option connection_string_or_opts [Array<String>] :addresses (["127.0.0.1:5672"]) list of addresses to select hostname and port from when connecting
|
102
101
|
# @option connection_string_or_opts [Integer] :port (5672) Port RabbitMQ listens on
|
103
102
|
# @option connection_string_or_opts [String] :username ("guest") Username
|
104
103
|
# @option connection_string_or_opts [String] :password ("guest") Password
|
105
104
|
# @option connection_string_or_opts [String] :vhost ("/") Virtual host to use
|
106
|
-
# @option connection_string_or_opts [Integer] :heartbeat (
|
105
|
+
# @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).
|
107
106
|
# @option connection_string_or_opts [Integer] :network_recovery_interval (4) Recovery interval periodic network recovery will use. This includes initial pause after network failure.
|
108
107
|
# @option connection_string_or_opts [Boolean] :tls (false) Should TLS/SSL be used?
|
109
108
|
# @option connection_string_or_opts [String] :tls_cert (nil) Path to client TLS/SSL certificate file (.pem)
|
110
109
|
# @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem)
|
111
110
|
# @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
|
112
|
-
# @option connection_string_or_opts [
|
113
|
-
# @option connection_string_or_opts [
|
111
|
+
# @option connection_string_or_opts [String] :verify_peer (true) Whether TLS peer verification should be performed
|
112
|
+
# @option connection_string_or_opts [Symbol] :tls_version (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2)
|
113
|
+
# @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.
|
114
|
+
# @option connection_string_or_opts [Integer] :continuation_timeout (15000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds.
|
115
|
+
# @option connection_string_or_opts [Integer] :connection_timeout (30) Timeout in seconds for connecting to the server.
|
116
|
+
# @option connection_string_or_opts [Integer] :read_timeout (30) TCP socket read timeout in seconds. If heartbeats are disabled this will be ignored.
|
117
|
+
# @option connection_string_or_opts [Integer] :write_timeout (30) TCP socket write timeout in seconds.
|
118
|
+
# @option connection_string_or_opts [Proc] :hosts_shuffle_strategy a callable that reorders a list of host strings, defaults to Array#shuffle
|
119
|
+
# @option connection_string_or_opts [Proc] :recovery_completed a callable that will be called when a network recovery is performed
|
120
|
+
# @option connection_string_or_opts [Logger] :logger The logger. If missing, one is created using :log_file and :log_level.
|
121
|
+
# @option connection_string_or_opts [IO, String] :log_file The file or path to use when creating a logger. Defaults to STDOUT.
|
122
|
+
# @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.
|
123
|
+
# @option connection_string_or_opts [Integer] :log_level The log level to use when creating a logger. Defaults to LOGGER::WARN
|
124
|
+
# @option connection_string_or_opts [Boolean] :automatically_recover (true) Should automatically recover from network failures?
|
125
|
+
# @option connection_string_or_opts [Integer] :recovery_attempts (nil) Max number of recovery attempts, nil means forever
|
126
|
+
# @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.
|
127
|
+
# @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)?
|
128
|
+
# @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.
|
114
129
|
#
|
115
130
|
# @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL
|
116
131
|
# @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use
|
132
|
+
# @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.
|
117
133
|
#
|
118
134
|
# @see http://rubybunny.info/articles/connecting.html Connecting to RabbitMQ guide
|
119
135
|
# @see http://rubybunny.info/articles/tls.html TLS/SSL guide
|
120
136
|
# @api public
|
121
|
-
def initialize(connection_string_or_opts =
|
122
|
-
opts = case (
|
137
|
+
def initialize(connection_string_or_opts = ENV['RABBITMQ_URL'], optz = Hash.new)
|
138
|
+
opts = case (connection_string_or_opts)
|
123
139
|
when nil then
|
124
140
|
Hash.new
|
125
141
|
when String then
|
126
|
-
self.class.parse_uri(
|
142
|
+
self.class.parse_uri(connection_string_or_opts)
|
127
143
|
when Hash then
|
128
144
|
connection_string_or_opts
|
129
145
|
end.merge(optz)
|
130
146
|
|
147
|
+
@default_hosts_shuffle_strategy = Proc.new { |hosts| hosts.shuffle }
|
148
|
+
|
131
149
|
@opts = opts
|
132
|
-
|
133
|
-
|
150
|
+
log_file = opts[:log_file] || opts[:logfile] || STDOUT
|
151
|
+
log_level = opts[:log_level] || ENV["BUNNY_LOG_LEVEL"] || Logger::WARN
|
152
|
+
# we might need to log a warning about ill-formatted IPv6 address but
|
153
|
+
# progname includes hostname, so init like this first
|
154
|
+
@logger = opts.fetch(:logger, init_default_logger_without_progname(log_file, log_level))
|
155
|
+
|
156
|
+
@addresses = self.addresses_from(opts)
|
157
|
+
@address_index = 0
|
158
|
+
|
159
|
+
@transport = nil
|
134
160
|
@user = self.username_from(opts)
|
135
161
|
@pass = self.password_from(opts)
|
136
162
|
@vhost = self.vhost_from(opts)
|
137
|
-
@logfile = opts[:log_file] || opts[:logfile] || STDOUT
|
138
163
|
@threaded = opts.fetch(:threaded, true)
|
139
164
|
|
140
|
-
|
165
|
+
# re-init, see above
|
166
|
+
@logger = opts.fetch(:logger, init_default_logger(log_file, log_level))
|
167
|
+
|
168
|
+
validate_connection_options(opts)
|
169
|
+
@last_connection_error = nil
|
141
170
|
|
142
171
|
# should automatic recovery from network failures be used?
|
143
172
|
@automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil?
|
@@ -145,12 +174,21 @@ module Bunny
|
|
145
174
|
else
|
146
175
|
opts[:automatically_recover] || opts[:automatic_recovery]
|
147
176
|
end
|
177
|
+
@recovering_from_network_failure = false
|
178
|
+
@max_recovery_attempts = opts[:recovery_attempts]
|
179
|
+
@recovery_attempts = @max_recovery_attempts
|
180
|
+
# When this is set, connection attempts won't be reset after
|
181
|
+
# successful reconnection. Some find this behavior more sensible
|
182
|
+
# than the per-failure attempt counter. MK.
|
183
|
+
@reset_recovery_attempt_counter_after_reconnection = opts.fetch(:reset_recovery_attempts_after_reconnection, true)
|
184
|
+
|
148
185
|
@network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
|
149
|
-
@recover_from_connection_close = opts.fetch(:recover_from_connection_close,
|
186
|
+
@recover_from_connection_close = opts.fetch(:recover_from_connection_close, true)
|
150
187
|
# in ms
|
151
|
-
@continuation_timeout
|
188
|
+
@continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT)
|
152
189
|
|
153
190
|
@status = :not_connected
|
191
|
+
@manually_closed = false
|
154
192
|
@blocked = false
|
155
193
|
|
156
194
|
# these are negotiated with the broker during the connection tuning phase
|
@@ -158,10 +196,14 @@ module Bunny
|
|
158
196
|
@client_channel_max = normalize_client_channel_max(opts.fetch(:channel_max, DEFAULT_CHANNEL_MAX))
|
159
197
|
# will be-renegotiated during connection tuning steps. MK.
|
160
198
|
@channel_max = @client_channel_max
|
199
|
+
@heartbeat_sender = nil
|
161
200
|
@client_heartbeat = self.heartbeat_from(opts)
|
162
201
|
|
163
|
-
|
164
|
-
@
|
202
|
+
client_props = opts[:properties] || opts[:client_properties] || {}
|
203
|
+
@connection_name = client_props[:connection_name] || opts[:connection_name]
|
204
|
+
@client_properties = DEFAULT_CLIENT_PROPERTIES.merge(client_props)
|
205
|
+
.merge(connection_name: connection_name)
|
206
|
+
@mechanism = normalize_auth_mechanism(opts.fetch(:auth_mechanism, "PLAIN"))
|
165
207
|
@credentials_encoder = credentials_encoder_for(@mechanism)
|
166
208
|
@locale = @opts.fetch(:locale, DEFAULT_LOCALE)
|
167
209
|
|
@@ -173,12 +215,26 @@ module Bunny
|
|
173
215
|
# the non-reentrant Ruby mutexes. MK.
|
174
216
|
@transport_mutex = @mutex_impl.new
|
175
217
|
@status_mutex = @mutex_impl.new
|
218
|
+
@address_index_mutex = @mutex_impl.new
|
219
|
+
|
176
220
|
@channels = Hash.new
|
221
|
+
@recovery_completed = opts[:recovery_completed]
|
177
222
|
|
178
|
-
@
|
223
|
+
@session_error_handler = opts.fetch(:session_error_handler, Thread.current)
|
179
224
|
|
180
225
|
self.reset_continuations
|
181
226
|
self.initialize_transport
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
def validate_connection_options(options)
|
231
|
+
if options[:hosts] && options[:addresses]
|
232
|
+
raise ArgumentError, "Connection options can't contain hosts and addresses at the same time"
|
233
|
+
end
|
234
|
+
|
235
|
+
if (options[:host] || options[:hostname]) && (options[:hosts] || options[:addresses])
|
236
|
+
@logger.warn "Connection options contain both a host and an array of hosts (addresses), please pick one."
|
237
|
+
end
|
182
238
|
end
|
183
239
|
|
184
240
|
# @return [String] RabbitMQ hostname (or IP address) used
|
@@ -190,9 +246,13 @@ module Bunny
|
|
190
246
|
# @return [String] Virtual host used
|
191
247
|
def virtual_host; self.vhost; end
|
192
248
|
|
193
|
-
# @
|
249
|
+
# @deprecated
|
250
|
+
# @return [Integer] Heartbeat timeout (not interval) used
|
194
251
|
def heartbeat_interval; self.heartbeat; end
|
195
252
|
|
253
|
+
# @return [Integer] Heartbeat timeout used
|
254
|
+
def heartbeat_timeout; self.heartbeat; end
|
255
|
+
|
196
256
|
# @return [Boolean] true if this connection uses TLS (SSL)
|
197
257
|
def uses_tls?
|
198
258
|
@transport.uses_tls?
|
@@ -210,6 +270,18 @@ module Bunny
|
|
210
270
|
@threaded
|
211
271
|
end
|
212
272
|
|
273
|
+
def host
|
274
|
+
@transport ? @transport.host : host_from_address(@addresses[@address_index])
|
275
|
+
end
|
276
|
+
|
277
|
+
def port
|
278
|
+
@transport ? @transport.port : port_from_address(@addresses[@address_index])
|
279
|
+
end
|
280
|
+
|
281
|
+
def reset_address_index
|
282
|
+
@address_index_mutex.synchronize { @address_index = 0 }
|
283
|
+
end
|
284
|
+
|
213
285
|
# @private
|
214
286
|
attr_reader :mutex_impl
|
215
287
|
|
@@ -241,37 +313,53 @@ module Bunny
|
|
241
313
|
self.reset_continuations
|
242
314
|
|
243
315
|
begin
|
244
|
-
|
245
|
-
|
246
|
-
|
316
|
+
begin
|
317
|
+
# close existing transport if we have one,
|
318
|
+
# to not leak sockets
|
319
|
+
@transport.maybe_initialize_socket
|
247
320
|
|
248
|
-
|
249
|
-
|
321
|
+
@transport.post_initialize_socket
|
322
|
+
@transport.connect
|
250
323
|
|
251
|
-
|
252
|
-
|
253
|
-
end
|
324
|
+
self.init_connection
|
325
|
+
self.open_connection
|
254
326
|
|
255
|
-
|
256
|
-
|
327
|
+
@reader_loop = nil
|
328
|
+
self.start_reader_loop if threaded?
|
257
329
|
|
258
|
-
|
259
|
-
|
330
|
+
rescue TCPConnectionFailed => e
|
331
|
+
@logger.warn e.message
|
332
|
+
self.initialize_transport
|
333
|
+
@logger.warn "Will try to connect to the next endpoint in line: #{@transport.host}:#{@transport.port}"
|
260
334
|
|
261
|
-
|
262
|
-
|
335
|
+
return self.start
|
336
|
+
rescue
|
337
|
+
@status_mutex.synchronize { @status = :not_connected }
|
338
|
+
raise
|
339
|
+
end
|
340
|
+
rescue HostListDepleted
|
341
|
+
self.reset_address_index
|
263
342
|
@status_mutex.synchronize { @status = :not_connected }
|
264
|
-
raise
|
343
|
+
raise TCPConnectionFailedForAllHosts
|
265
344
|
end
|
345
|
+
@status_mutex.synchronize { @manually_closed = false }
|
266
346
|
|
267
347
|
self
|
268
348
|
end
|
269
349
|
|
270
|
-
|
350
|
+
def update_secret(value, reason)
|
351
|
+
@transport.send_frame(AMQ::Protocol::Connection::UpdateSecret.encode(value, reason))
|
352
|
+
@last_update_secret_ok = wait_on_continuations
|
353
|
+
raise_if_continuation_resulted_in_a_connection_error!
|
354
|
+
|
355
|
+
@last_update_secret_ok
|
356
|
+
end
|
357
|
+
|
358
|
+
# Socket operation write timeout used by this connection
|
271
359
|
# @return [Integer]
|
272
360
|
# @private
|
273
|
-
def
|
274
|
-
@transport.
|
361
|
+
def transport_write_timeout
|
362
|
+
@transport.write_timeout
|
275
363
|
end
|
276
364
|
|
277
365
|
# Opens a new channel and returns it. This method will block the calling
|
@@ -279,14 +367,16 @@ module Bunny
|
|
279
367
|
# opened (this operation is very fast and inexpensive).
|
280
368
|
#
|
281
369
|
# @return [Bunny::Channel] Newly opened channel
|
282
|
-
def create_channel(n = nil, consumer_pool_size = 1)
|
370
|
+
def create_channel(n = nil, consumer_pool_size = 1, consumer_pool_abort_on_exception = false, consumer_pool_shutdown_timeout = 60)
|
283
371
|
raise ArgumentError, "channel number 0 is reserved in the protocol and cannot be used" if 0 == n
|
372
|
+
raise ConnectionAlreadyClosed if manually_closed?
|
373
|
+
raise RuntimeError, "this connection is not open. Was Bunny::Session#start invoked? Is automatic recovery enabled?" if !connected?
|
284
374
|
|
285
375
|
@channel_mutex.synchronize do
|
286
376
|
if n && (ch = @channels[n])
|
287
377
|
ch
|
288
378
|
else
|
289
|
-
ch = Bunny::Channel.new(self, n, ConsumerWorkPool.new(consumer_pool_size || 1))
|
379
|
+
ch = Bunny::Channel.new(self, n, ConsumerWorkPool.new(consumer_pool_size || 1, consumer_pool_abort_on_exception, consumer_pool_shutdown_timeout))
|
290
380
|
ch.open
|
291
381
|
ch
|
292
382
|
end
|
@@ -295,19 +385,26 @@ module Bunny
|
|
295
385
|
alias channel create_channel
|
296
386
|
|
297
387
|
# Closes the connection. This involves closing all of its channels.
|
298
|
-
def close
|
388
|
+
def close(await_response = true)
|
299
389
|
@status_mutex.synchronize { @status = :closing }
|
300
390
|
|
301
391
|
ignoring_io_errors do
|
302
392
|
if @transport.open?
|
393
|
+
@logger.debug "Transport is still open..."
|
303
394
|
close_all_channels
|
304
395
|
|
305
|
-
|
396
|
+
@logger.debug "Will close all channels...."
|
397
|
+
self.close_connection(await_response)
|
306
398
|
end
|
307
399
|
|
308
400
|
clean_up_on_shutdown
|
309
401
|
end
|
310
|
-
@status_mutex.synchronize
|
402
|
+
@status_mutex.synchronize do
|
403
|
+
@status = :closed
|
404
|
+
@manually_closed = true
|
405
|
+
end
|
406
|
+
@logger.debug "Connection is closed"
|
407
|
+
true
|
311
408
|
end
|
312
409
|
alias stop close
|
313
410
|
|
@@ -342,6 +439,11 @@ module Bunny
|
|
342
439
|
@status_mutex.synchronize { @status == :closed }
|
343
440
|
end
|
344
441
|
|
442
|
+
# @return [Boolean] true if this AMQP 0.9.1 connection has been closed by the user (as opposed to the server)
|
443
|
+
def manually_closed?
|
444
|
+
@status_mutex.synchronize { @manually_closed == true }
|
445
|
+
end
|
446
|
+
|
345
447
|
# @return [Boolean] true if this AMQP 0.9.1 connection is open
|
346
448
|
def open?
|
347
449
|
@status_mutex.synchronize do
|
@@ -355,40 +457,6 @@ module Bunny
|
|
355
457
|
@automatically_recover
|
356
458
|
end
|
357
459
|
|
358
|
-
#
|
359
|
-
# Backwards compatibility
|
360
|
-
#
|
361
|
-
|
362
|
-
# @private
|
363
|
-
def queue(*args)
|
364
|
-
@default_channel.queue(*args)
|
365
|
-
end
|
366
|
-
|
367
|
-
# @private
|
368
|
-
def direct(*args)
|
369
|
-
@default_channel.direct(*args)
|
370
|
-
end
|
371
|
-
|
372
|
-
# @private
|
373
|
-
def fanout(*args)
|
374
|
-
@default_channel.fanout(*args)
|
375
|
-
end
|
376
|
-
|
377
|
-
# @private
|
378
|
-
def topic(*args)
|
379
|
-
@default_channel.topic(*args)
|
380
|
-
end
|
381
|
-
|
382
|
-
# @private
|
383
|
-
def headers(*args)
|
384
|
-
@default_channel.headers(*args)
|
385
|
-
end
|
386
|
-
|
387
|
-
# @private
|
388
|
-
def exchange(*args)
|
389
|
-
@default_channel.exchange(*args)
|
390
|
-
end
|
391
|
-
|
392
460
|
# Defines a callback that will be executed when RabbitMQ blocks the connection
|
393
461
|
# because it is running low on memory or disk space (as configured via config file
|
394
462
|
# and/or rabbitmqctl).
|
@@ -422,7 +490,7 @@ module Bunny
|
|
422
490
|
# @param [String] uri amqp or amqps URI to parse
|
423
491
|
# @return [Hash] Parsed URI as a hash
|
424
492
|
def self.parse_uri(uri)
|
425
|
-
AMQ::Settings.
|
493
|
+
AMQ::Settings.configure(uri)
|
426
494
|
end
|
427
495
|
|
428
496
|
# Checks if a queue with given name exists.
|
@@ -472,16 +540,18 @@ module Bunny
|
|
472
540
|
|
473
541
|
# @private
|
474
542
|
def open_channel(ch)
|
475
|
-
|
476
|
-
|
543
|
+
@channel_mutex.synchronize do
|
544
|
+
n = ch.number
|
545
|
+
self.register_channel(ch)
|
477
546
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
547
|
+
@transport_mutex.synchronize do
|
548
|
+
@transport.send_frame(AMQ::Protocol::Channel::Open.encode(n, AMQ::Protocol::EMPTY_STRING))
|
549
|
+
end
|
550
|
+
@last_channel_open_ok = wait_on_continuations
|
551
|
+
raise_if_continuation_resulted_in_a_connection_error!
|
483
552
|
|
484
|
-
|
553
|
+
@last_channel_open_ok
|
554
|
+
end
|
485
555
|
end
|
486
556
|
|
487
557
|
# @private
|
@@ -494,23 +564,38 @@ module Bunny
|
|
494
564
|
raise_if_continuation_resulted_in_a_connection_error!
|
495
565
|
|
496
566
|
self.unregister_channel(ch)
|
567
|
+
self.release_channel_id(ch.id)
|
497
568
|
@last_channel_close_ok
|
498
569
|
end
|
499
570
|
end
|
500
571
|
|
572
|
+
# @private
|
573
|
+
def find_channel(number)
|
574
|
+
@channels[number]
|
575
|
+
end
|
576
|
+
|
577
|
+
# @private
|
578
|
+
def synchronised_find_channel(number)
|
579
|
+
@channel_mutex.synchronize { @channels[number] }
|
580
|
+
end
|
581
|
+
|
501
582
|
# @private
|
502
583
|
def close_all_channels
|
503
|
-
@
|
504
|
-
|
584
|
+
@channel_mutex.synchronize do
|
585
|
+
@channels.reject {|n, ch| n == 0 || !ch.open? }.each do |_, ch|
|
586
|
+
Bunny::Timeout.timeout(@transport.disconnect_timeout, ClientTimeout) { ch.close }
|
587
|
+
end
|
505
588
|
end
|
506
589
|
end
|
507
590
|
|
508
591
|
# @private
|
509
|
-
def close_connection(
|
592
|
+
def close_connection(await_response = true)
|
510
593
|
if @transport.open?
|
594
|
+
@logger.debug "Transport is still open"
|
511
595
|
@transport.send_frame(AMQ::Protocol::Connection::Close.encode(200, "Goodbye", 0, 0))
|
512
596
|
|
513
|
-
if
|
597
|
+
if await_response
|
598
|
+
@logger.debug "Waiting for a connection.close-ok..."
|
514
599
|
@last_connection_close_ok = wait_on_continuations
|
515
600
|
end
|
516
601
|
end
|
@@ -529,7 +614,7 @@ module Bunny
|
|
529
614
|
#
|
530
615
|
# @private
|
531
616
|
def handle_frame(ch_number, method)
|
532
|
-
@logger.debug "Session#handle_frame on #{ch_number}: #{method.inspect}"
|
617
|
+
@logger.debug { "Session#handle_frame on #{ch_number}: #{method.inspect}" }
|
533
618
|
case method
|
534
619
|
when AMQ::Protocol::Channel::OpenOk then
|
535
620
|
@continuations.push(method)
|
@@ -560,17 +645,24 @@ module Bunny
|
|
560
645
|
when AMQ::Protocol::Connection::Unblocked then
|
561
646
|
@blocked = false
|
562
647
|
@unblock_callback.call(method) if @unblock_callback
|
648
|
+
when AMQ::Protocol::Connection::UpdateSecretOk then
|
649
|
+
@continuations.push(method)
|
563
650
|
when AMQ::Protocol::Channel::Close then
|
564
651
|
begin
|
565
|
-
ch =
|
652
|
+
ch = synchronised_find_channel(ch_number)
|
653
|
+
# this includes sending a channel.close-ok and
|
654
|
+
# potentially invoking a user-provided callback,
|
655
|
+
# avoid doing that while holding a mutex lock. MK.
|
566
656
|
ch.handle_method(method)
|
567
657
|
ensure
|
658
|
+
# synchronises on @channel_mutex under the hood
|
568
659
|
self.unregister_channel(ch)
|
569
660
|
end
|
570
661
|
when AMQ::Protocol::Basic::GetEmpty then
|
571
|
-
|
662
|
+
ch = find_channel(ch_number)
|
663
|
+
ch.handle_basic_get_empty(method)
|
572
664
|
else
|
573
|
-
if ch =
|
665
|
+
if ch = find_channel(ch_number)
|
574
666
|
ch.handle_method(method)
|
575
667
|
else
|
576
668
|
@logger.warn "Channel #{ch_number} is not open on this connection!"
|
@@ -614,15 +706,18 @@ module Bunny
|
|
614
706
|
begin
|
615
707
|
@recovering_from_network_failure = true
|
616
708
|
if recoverable_network_failure?(exception)
|
617
|
-
|
618
|
-
@
|
619
|
-
ch
|
709
|
+
announce_network_failure_recovery
|
710
|
+
@channel_mutex.synchronize do
|
711
|
+
@channels.each do |n, ch|
|
712
|
+
ch.maybe_kill_consumer_work_pool!
|
713
|
+
end
|
620
714
|
end
|
715
|
+
@reader_loop.stop if @reader_loop
|
621
716
|
maybe_shutdown_heartbeat_sender
|
622
717
|
|
623
718
|
recover_from_network_failure
|
624
719
|
else
|
625
|
-
|
720
|
+
@logger.error "Exception #{exception.message} is considered unrecoverable..."
|
626
721
|
end
|
627
722
|
ensure
|
628
723
|
@recovering_from_network_failure = false
|
@@ -632,7 +727,8 @@ module Bunny
|
|
632
727
|
|
633
728
|
# @private
|
634
729
|
def recoverable_network_failure?(exception)
|
635
|
-
#
|
730
|
+
# No reasonably smart strategy was suggested in a few years.
|
731
|
+
# So just recover unconditionally. MK.
|
636
732
|
true
|
637
733
|
end
|
638
734
|
|
@@ -641,38 +737,101 @@ module Bunny
|
|
641
737
|
@recovering_from_network_failure
|
642
738
|
end
|
643
739
|
|
740
|
+
# @private
|
741
|
+
def announce_network_failure_recovery
|
742
|
+
if recovery_attempts_limited?
|
743
|
+
@logger.warn "Will recover from a network failure (#{@recovery_attempts} out of #{@max_recovery_attempts} left)..."
|
744
|
+
else
|
745
|
+
@logger.warn "Will recover from a network failure (no retry limit)..."
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
644
749
|
# @private
|
645
750
|
def recover_from_network_failure
|
646
|
-
|
647
|
-
|
648
|
-
@logger.debug "About to start connection recovery..."
|
649
|
-
self.initialize_transport
|
650
|
-
self.start
|
751
|
+
sleep @network_recovery_interval
|
752
|
+
@logger.debug "Will attempt connection recovery..."
|
651
753
|
|
652
|
-
|
653
|
-
|
754
|
+
self.initialize_transport
|
755
|
+
|
756
|
+
@logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
|
757
|
+
self.start
|
654
758
|
|
655
|
-
|
759
|
+
if open?
|
760
|
+
|
761
|
+
@recovering_from_network_failure = false
|
762
|
+
@logger.debug "Connection is now open"
|
763
|
+
if @reset_recovery_attempt_counter_after_reconnection
|
764
|
+
@logger.debug "Resetting recovery attempt counter after successful reconnection"
|
765
|
+
reset_recovery_attempt_counter!
|
766
|
+
else
|
767
|
+
@logger.debug "Not resetting recovery attempt counter after successful reconnection, as configured"
|
768
|
+
end
|
769
|
+
|
770
|
+
recover_channels
|
771
|
+
notify_of_recovery_completion
|
772
|
+
end
|
773
|
+
rescue HostListDepleted
|
774
|
+
reset_address_index
|
775
|
+
retry
|
776
|
+
rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError, SystemCallError, Timeout::Error => e
|
777
|
+
@logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds"
|
778
|
+
if should_retry_recovery?
|
779
|
+
decrement_recovery_attemp_counter!
|
780
|
+
if recoverable_network_failure?(e)
|
781
|
+
announce_network_failure_recovery
|
782
|
+
retry
|
656
783
|
end
|
657
|
-
|
658
|
-
@logger.
|
659
|
-
|
660
|
-
|
784
|
+
else
|
785
|
+
@logger.error "Ran out of recovery attempts (limit set to #{@max_recovery_attempts}), giving up"
|
786
|
+
@transport.close
|
787
|
+
self.close(false)
|
788
|
+
@manually_closed = false
|
661
789
|
end
|
662
790
|
end
|
663
791
|
|
664
792
|
# @private
|
665
|
-
def
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
793
|
+
def recovery_attempts_limited?
|
794
|
+
!!@max_recovery_attempts
|
795
|
+
end
|
796
|
+
|
797
|
+
# @private
|
798
|
+
def should_retry_recovery?
|
799
|
+
!recovery_attempts_limited? || @recovery_attempts > 1
|
800
|
+
end
|
801
|
+
|
802
|
+
# @private
|
803
|
+
def decrement_recovery_attemp_counter!
|
804
|
+
if @recovery_attempts
|
805
|
+
@recovery_attempts -= 1
|
806
|
+
@logger.debug "#{@recovery_attempts} recovery attempts left"
|
807
|
+
end
|
808
|
+
@recovery_attempts
|
809
|
+
end
|
810
|
+
|
811
|
+
# @private
|
812
|
+
def reset_recovery_attempt_counter!
|
813
|
+
@recovery_attempts = @max_recovery_attempts
|
814
|
+
end
|
671
815
|
|
672
|
-
|
816
|
+
# @private
|
817
|
+
def recover_channels
|
818
|
+
@channel_mutex.synchronize do
|
819
|
+
@channels.each do |n, ch|
|
820
|
+
ch.open
|
821
|
+
ch.recover_from_network_failure
|
822
|
+
end
|
673
823
|
end
|
674
824
|
end
|
675
825
|
|
826
|
+
def after_recovery_completed(&block)
|
827
|
+
@recovery_completed = block
|
828
|
+
end
|
829
|
+
|
830
|
+
# @private
|
831
|
+
def notify_of_recovery_completion
|
832
|
+
@recovery_completed.call if @recovery_completed
|
833
|
+
end
|
834
|
+
|
676
835
|
# @private
|
677
836
|
def instantiate_connection_level_exception(frame)
|
678
837
|
case frame
|
@@ -708,7 +867,7 @@ module Bunny
|
|
708
867
|
|
709
868
|
clean_up_on_shutdown
|
710
869
|
if threaded?
|
711
|
-
@
|
870
|
+
@session_error_handler.raise(@last_connection_error)
|
712
871
|
else
|
713
872
|
raise @last_connection_error
|
714
873
|
end
|
@@ -719,7 +878,7 @@ module Bunny
|
|
719
878
|
shut_down_all_consumer_work_pools!
|
720
879
|
maybe_shutdown_reader_loop
|
721
880
|
maybe_shutdown_heartbeat_sender
|
722
|
-
rescue ShutdownSignal =>
|
881
|
+
rescue ShutdownSignal => _sse
|
723
882
|
# no-op
|
724
883
|
rescue Exception => e
|
725
884
|
@logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}"
|
@@ -729,8 +888,18 @@ module Bunny
|
|
729
888
|
end
|
730
889
|
|
731
890
|
# @private
|
732
|
-
def
|
733
|
-
|
891
|
+
def addresses_from(options)
|
892
|
+
shuffle_strategy = options.fetch(:hosts_shuffle_strategy, @default_hosts_shuffle_strategy)
|
893
|
+
|
894
|
+
addresses = options[:host] || options[:hostname] || options[:addresses] ||
|
895
|
+
options[:hosts] || ["#{DEFAULT_HOST}:#{port_from(options)}"]
|
896
|
+
addresses = [addresses] unless addresses.is_a? Array
|
897
|
+
|
898
|
+
addrs = addresses.map do |address|
|
899
|
+
host_with_port?(address) ? address : "#{address}:#{port_from(@opts)}"
|
900
|
+
end
|
901
|
+
|
902
|
+
shuffle_strategy.call(addrs)
|
734
903
|
end
|
735
904
|
|
736
905
|
# @private
|
@@ -744,6 +913,63 @@ module Bunny
|
|
744
913
|
options.fetch(:port, fallback)
|
745
914
|
end
|
746
915
|
|
916
|
+
# @private
|
917
|
+
def host_with_port?(address)
|
918
|
+
# we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671
|
919
|
+
last_colon = address.rindex(":")
|
920
|
+
last_closing_square_bracket = address.rindex("]")
|
921
|
+
|
922
|
+
if last_closing_square_bracket.nil?
|
923
|
+
address.include?(":")
|
924
|
+
else
|
925
|
+
last_closing_square_bracket < last_colon
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
# @private
|
930
|
+
def host_from_address(address)
|
931
|
+
# we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671
|
932
|
+
last_colon = address.rindex(":")
|
933
|
+
last_closing_square_bracket = address.rindex("]")
|
934
|
+
|
935
|
+
if last_closing_square_bracket.nil?
|
936
|
+
parts = address.split(":")
|
937
|
+
# this looks like an unquoted IPv6 address, so emit a warning
|
938
|
+
if parts.size > 2
|
939
|
+
@logger.warn "Address #{address} looks like an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]"
|
940
|
+
end
|
941
|
+
return parts[0]
|
942
|
+
end
|
943
|
+
|
944
|
+
if last_closing_square_bracket < last_colon
|
945
|
+
# there is a port
|
946
|
+
address[0, last_colon]
|
947
|
+
elsif last_closing_square_bracket > last_colon
|
948
|
+
address
|
949
|
+
end
|
950
|
+
end
|
951
|
+
|
952
|
+
# @private
|
953
|
+
def port_from_address(address)
|
954
|
+
# we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671
|
955
|
+
last_colon = address.rindex(":")
|
956
|
+
last_closing_square_bracket = address.rindex("]")
|
957
|
+
|
958
|
+
if last_closing_square_bracket.nil?
|
959
|
+
parts = address.split(":")
|
960
|
+
# this looks like an unquoted IPv6 address, so emit a warning
|
961
|
+
if parts.size > 2
|
962
|
+
@logger.warn "Address #{address} looks like an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]"
|
963
|
+
end
|
964
|
+
return parts[1].to_i
|
965
|
+
end
|
966
|
+
|
967
|
+
if last_closing_square_bracket < last_colon
|
968
|
+
# there is a port
|
969
|
+
address[(last_colon + 1)..-1].to_i
|
970
|
+
end
|
971
|
+
end
|
972
|
+
|
747
973
|
# @private
|
748
974
|
def vhost_from(options)
|
749
975
|
options[:virtual_host] || options[:vhost] || DEFAULT_VHOST
|
@@ -761,7 +987,7 @@ module Bunny
|
|
761
987
|
|
762
988
|
# @private
|
763
989
|
def heartbeat_from(options)
|
764
|
-
options[:heartbeat] || options[:
|
990
|
+
options[:heartbeat] || options[:heartbeat_timeout] || options[:requested_heartbeat] || options[:heartbeat_interval] || DEFAULT_HEARTBEAT
|
765
991
|
end
|
766
992
|
|
767
993
|
# @private
|
@@ -798,7 +1024,7 @@ module Bunny
|
|
798
1024
|
|
799
1025
|
# @private
|
800
1026
|
def reader_loop
|
801
|
-
@reader_loop ||= ReaderLoop.new(@transport, self,
|
1027
|
+
@reader_loop ||= ReaderLoop.new(@transport, self, @session_error_handler)
|
802
1028
|
end
|
803
1029
|
|
804
1030
|
# @private
|
@@ -877,7 +1103,7 @@ module Bunny
|
|
877
1103
|
end
|
878
1104
|
end
|
879
1105
|
|
880
|
-
# Sends multiple frames, one
|
1106
|
+
# Sends multiple frames, in one go. For thread safety this method takes a channel
|
881
1107
|
# object and synchronizes on it.
|
882
1108
|
#
|
883
1109
|
# @private
|
@@ -886,10 +1112,18 @@ module Bunny
|
|
886
1112
|
# threads publish on the same channel aggressively, at some point frames will be
|
887
1113
|
# delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
|
888
1114
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
889
|
-
# locking. Note that "single frame" methods do not need this kind of synchronization
|
1115
|
+
# locking. Note that "single frame" methods technically do not need this kind of synchronization
|
1116
|
+
# (no incorrect frame interleaving of the same kind as with basic.publish isn't possible) but we
|
1117
|
+
# still recommend not sharing channels between threads except for consumer-only cases in the docs. MK.
|
890
1118
|
channel.synchronize do
|
891
|
-
|
892
|
-
|
1119
|
+
# see rabbitmq/rabbitmq-server#156
|
1120
|
+
if open?
|
1121
|
+
data = frames.reduce("") { |acc, frame| acc << frame.encode }
|
1122
|
+
@transport.write(data)
|
1123
|
+
signal_activity!
|
1124
|
+
else
|
1125
|
+
raise ConnectionClosedError.new(frames)
|
1126
|
+
end
|
893
1127
|
end
|
894
1128
|
end # send_frameset(frames)
|
895
1129
|
|
@@ -903,10 +1137,14 @@ module Bunny
|
|
903
1137
|
# threads publish on the same channel aggressively, at some point frames will be
|
904
1138
|
# delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception.
|
905
1139
|
# If we synchronize on the channel, however, this is both thread safe and pretty fine-grained
|
906
|
-
# locking.
|
1140
|
+
# locking. See a note about "single frame" methods in a comment in `send_frameset`. MK.
|
907
1141
|
channel.synchronize do
|
908
|
-
|
909
|
-
|
1142
|
+
if open?
|
1143
|
+
frames.each { |frame| self.send_frame_without_timeout(frame, false) }
|
1144
|
+
signal_activity!
|
1145
|
+
else
|
1146
|
+
raise ConnectionClosedError.new(frames)
|
1147
|
+
end
|
910
1148
|
end
|
911
1149
|
end # send_frameset_without_timeout(frames)
|
912
1150
|
|
@@ -926,7 +1164,12 @@ module Bunny
|
|
926
1164
|
# @return [String]
|
927
1165
|
# @api public
|
928
1166
|
def to_s
|
929
|
-
"
|
1167
|
+
oid = ("0x%x" % (self.object_id << 1))
|
1168
|
+
"#<#{self.class.name}:#{oid} #{@user}@#{host}:#{port}, vhost=#{@vhost}, addresses=[#{@addresses.join(',')}]>"
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
def inspect
|
1172
|
+
to_s
|
930
1173
|
end
|
931
1174
|
|
932
1175
|
protected
|
@@ -959,13 +1202,11 @@ module Bunny
|
|
959
1202
|
fr
|
960
1203
|
# frame timeout means the broker has closed the TCP connection, which it
|
961
1204
|
# does per 0.9.1 spec.
|
962
|
-
rescue
|
1205
|
+
rescue
|
963
1206
|
nil
|
964
1207
|
end
|
965
1208
|
if frame.nil?
|
966
|
-
|
967
|
-
@logger.error "RabbitMQ closed TCP connection before AMQP 0.9.1 connection was finalized. Most likely this means authentication failure."
|
968
|
-
raise Bunny::PossibleAuthenticationFailureError.new(self.user, self.vhost, self.password.size)
|
1209
|
+
raise TCPConnectionFailed.new('An empty frame was received while opening the connection. In RabbitMQ <= 3.1 this could mean an authentication issue.')
|
969
1210
|
end
|
970
1211
|
|
971
1212
|
response = frame.decode_payload
|
@@ -987,19 +1228,28 @@ module Bunny
|
|
987
1228
|
else
|
988
1229
|
negotiate_value(@client_heartbeat, connection_tune.heartbeat)
|
989
1230
|
end
|
990
|
-
@logger.debug "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}"
|
1231
|
+
@logger.debug { "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}" }
|
991
1232
|
@logger.info "Heartbeat interval used (in seconds): #{@heartbeat}"
|
992
1233
|
|
1234
|
+
# We set the read_write_timeout to twice the heartbeat value,
|
1235
|
+
# and then some padding for edge cases.
|
1236
|
+
# This allows us to miss a single heartbeat before we time out the socket.
|
1237
|
+
# If heartbeats are disabled, assume that TCP keepalives or a similar mechanism will be used
|
1238
|
+
# and disable socket read timeouts. See ruby-amqp/bunny#551.
|
1239
|
+
@transport.read_timeout = @heartbeat * 2.2
|
1240
|
+
@logger.debug { "Will use socket read timeout of #{@transport.read_timeout.to_i} seconds" }
|
1241
|
+
|
993
1242
|
# if there are existing channels we've just recovered from
|
994
1243
|
# a network failure and need to fix the allocated set. See issue 205. MK.
|
995
1244
|
if @channels.empty?
|
1245
|
+
@logger.debug { "Initializing channel ID allocator with channel_max = #{@channel_max}" }
|
996
1246
|
@channel_id_allocator = ChannelIdAllocator.new(@channel_max)
|
997
1247
|
end
|
998
1248
|
|
999
1249
|
@transport.send_frame(AMQ::Protocol::Connection::TuneOk.encode(@channel_max, @frame_max, @heartbeat))
|
1000
|
-
@logger.debug "Sent connection.tune-ok with heartbeat interval = #{@heartbeat}, frame_max = #{@frame_max}, channel_max = #{@channel_max}"
|
1250
|
+
@logger.debug { "Sent connection.tune-ok with heartbeat interval = #{@heartbeat}, frame_max = #{@frame_max}, channel_max = #{@channel_max}" }
|
1001
1251
|
@transport.send_frame(AMQ::Protocol::Connection::Open.encode(self.vhost))
|
1002
|
-
@logger.debug "Sent connection.open with vhost = #{self.vhost}"
|
1252
|
+
@logger.debug { "Sent connection.open with vhost = #{self.vhost}" }
|
1003
1253
|
|
1004
1254
|
frame2 = begin
|
1005
1255
|
fr = @transport.read_next_frame
|
@@ -1009,13 +1259,11 @@ module Bunny
|
|
1009
1259
|
fr
|
1010
1260
|
# frame timeout means the broker has closed the TCP connection, which it
|
1011
1261
|
# does per 0.9.1 spec.
|
1012
|
-
rescue
|
1262
|
+
rescue
|
1013
1263
|
nil
|
1014
1264
|
end
|
1015
1265
|
if frame2.nil?
|
1016
|
-
|
1017
|
-
@logger.warn "RabbitMQ closed TCP connection before AMQP 0.9.1 connection was finalized. Most likely this means authentication failure."
|
1018
|
-
raise Bunny::PossibleAuthenticationFailureError.new(self.user, self.vhost, self.password.size)
|
1266
|
+
raise TCPConnectionFailed.new('An empty frame was received while opening the connection. In RabbitMQ <= 3.1 this could mean an authentication issue.')
|
1019
1267
|
end
|
1020
1268
|
connection_open_ok = frame2.decode_payload
|
1021
1269
|
|
@@ -1030,7 +1278,7 @@ module Bunny
|
|
1030
1278
|
begin
|
1031
1279
|
shut_down_all_consumer_work_pools!
|
1032
1280
|
maybe_shutdown_reader_loop
|
1033
|
-
rescue ShutdownSignal =>
|
1281
|
+
rescue ShutdownSignal => _sse
|
1034
1282
|
# no-op
|
1035
1283
|
rescue Exception => e
|
1036
1284
|
@logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}"
|
@@ -1038,7 +1286,11 @@ module Bunny
|
|
1038
1286
|
close_transport
|
1039
1287
|
end
|
1040
1288
|
|
1041
|
-
|
1289
|
+
if threaded?
|
1290
|
+
@session_error_handler.raise(e)
|
1291
|
+
else
|
1292
|
+
raise e
|
1293
|
+
end
|
1042
1294
|
else
|
1043
1295
|
raise "could not open connection: server did not respond with connection.open-ok but #{connection_open_ok.inspect} instead"
|
1044
1296
|
end
|
@@ -1051,7 +1303,7 @@ module Bunny
|
|
1051
1303
|
|
1052
1304
|
# @private
|
1053
1305
|
def negotiate_value(client_value, server_value)
|
1054
|
-
return server_value if
|
1306
|
+
return server_value if [:server, "server"].include?(client_value)
|
1055
1307
|
|
1056
1308
|
if client_value == 0 || server_value == 0
|
1057
1309
|
[client_value, server_value].max
|
@@ -1075,7 +1327,22 @@ module Bunny
|
|
1075
1327
|
|
1076
1328
|
# @private
|
1077
1329
|
def initialize_transport
|
1078
|
-
|
1330
|
+
if address = @addresses[ @address_index ]
|
1331
|
+
@address_index_mutex.synchronize { @address_index += 1 }
|
1332
|
+
@transport.close rescue nil # Let's make sure the previous transport socket is closed
|
1333
|
+
@transport = Transport.new(self,
|
1334
|
+
host_from_address(address),
|
1335
|
+
port_from_address(address),
|
1336
|
+
@opts.merge(:session_error_handler => @session_error_handler)
|
1337
|
+
)
|
1338
|
+
|
1339
|
+
# Reset the cached progname for the logger only when no logger was provided
|
1340
|
+
@default_logger.progname = self.to_s
|
1341
|
+
|
1342
|
+
@transport
|
1343
|
+
else
|
1344
|
+
raise HostListDepleted
|
1345
|
+
end
|
1079
1346
|
end
|
1080
1347
|
|
1081
1348
|
# @private
|
@@ -1123,12 +1390,22 @@ module Bunny
|
|
1123
1390
|
end
|
1124
1391
|
|
1125
1392
|
# @private
|
1126
|
-
def
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1393
|
+
def init_default_logger(logfile, level)
|
1394
|
+
@default_logger = begin
|
1395
|
+
lgr = ::Logger.new(logfile)
|
1396
|
+
lgr.level = normalize_log_level(level)
|
1397
|
+
lgr.progname = self.to_s
|
1398
|
+
lgr
|
1399
|
+
end
|
1400
|
+
end
|
1130
1401
|
|
1131
|
-
|
1402
|
+
# @private
|
1403
|
+
def init_default_logger_without_progname(logfile, level)
|
1404
|
+
@default_logger = begin
|
1405
|
+
lgr = ::Logger.new(logfile)
|
1406
|
+
lgr.level = normalize_log_level(level)
|
1407
|
+
lgr
|
1408
|
+
end
|
1132
1409
|
end
|
1133
1410
|
|
1134
1411
|
# @private
|
@@ -1152,6 +1429,7 @@ module Bunny
|
|
1152
1429
|
end
|
1153
1430
|
|
1154
1431
|
def normalize_client_channel_max(n)
|
1432
|
+
return CHANNEL_MAX_LIMIT if n.nil?
|
1155
1433
|
return CHANNEL_MAX_LIMIT if n > CHANNEL_MAX_LIMIT
|
1156
1434
|
|
1157
1435
|
case n
|
@@ -1162,6 +1440,17 @@ module Bunny
|
|
1162
1440
|
end
|
1163
1441
|
end
|
1164
1442
|
|
1443
|
+
def normalize_auth_mechanism(value)
|
1444
|
+
case value
|
1445
|
+
when [] then
|
1446
|
+
"PLAIN"
|
1447
|
+
when nil then
|
1448
|
+
"PLAIN"
|
1449
|
+
else
|
1450
|
+
value
|
1451
|
+
end
|
1452
|
+
end
|
1453
|
+
|
1165
1454
|
def ignoring_io_errors(&block)
|
1166
1455
|
begin
|
1167
1456
|
block.call
|