mongo 2.24.0 → 2.25.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/bin/mongo_console +2 -2
- data/lib/mongo/address/validator.rb +1 -1
- data/lib/mongo/address.rb +1 -3
- data/lib/mongo/auth/aws/conversation.rb +0 -4
- data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
- data/lib/mongo/auth/base.rb +1 -1
- data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
- data/lib/mongo/auth/scram_conversation_base.rb +1 -8
- data/lib/mongo/auth/stringprep.rb +1 -6
- data/lib/mongo/auth/user/view.rb +1 -1
- data/lib/mongo/bulk_write/result.rb +10 -2
- data/lib/mongo/bulk_write/result_combiner.rb +11 -4
- data/lib/mongo/bulk_write.rb +1 -1
- data/lib/mongo/client.rb +82 -12
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
- data/lib/mongo/cluster/sdam_flow.rb +4 -4
- data/lib/mongo/cluster/topology/base.rb +1 -1
- data/lib/mongo/cluster.rb +12 -28
- data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
- data/lib/mongo/collection/view/aggregation.rb +2 -4
- data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
- data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +14 -9
- data/lib/mongo/collection/view/map_reduce.rb +2 -11
- data/lib/mongo/collection/view/readable.rb +11 -7
- data/lib/mongo/collection/view/writable.rb +4 -4
- data/lib/mongo/collection/view.rb +8 -4
- data/lib/mongo/collection.rb +1 -1
- data/lib/mongo/config.rb +5 -0
- data/lib/mongo/crypt/auto_encrypter.rb +1 -1
- data/lib/mongo/crypt/binary.rb +7 -4
- data/lib/mongo/crypt/binding.rb +88 -10
- data/lib/mongo/crypt/context.rb +50 -19
- data/lib/mongo/crypt/encryption_io.rb +11 -5
- data/lib/mongo/crypt/handle.rb +17 -5
- data/lib/mongo/crypt/kms/credentials.rb +70 -31
- data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
- data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
- data/lib/mongo/crypt/kms.rb +12 -1
- data/lib/mongo/cursor.rb +8 -2
- data/lib/mongo/database/cursor_command_view.rb +95 -0
- data/lib/mongo/database.rb +144 -18
- data/lib/mongo/error/bulk_write_error.rb +29 -1
- data/lib/mongo/error/invalid_server_preference.rb +3 -0
- data/lib/mongo/error/invalid_uri.rb +5 -1
- data/lib/mongo/error/operation_failure.rb +42 -3
- data/lib/mongo/error/parser.rb +3 -3
- data/lib/mongo/error/sdam_error_detection.rb +2 -3
- data/lib/mongo/grid/fs_bucket.rb +27 -21
- data/lib/mongo/grid/stream/write.rb +2 -2
- data/lib/mongo/index/view.rb +13 -7
- data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
- data/lib/mongo/monitoring/event/command_failed.rb +1 -0
- data/lib/mongo/monitoring/event/command_started.rb +1 -0
- data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/secure.rb +12 -17
- data/lib/mongo/monitoring/event/server_closed.rb +1 -0
- data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/server_opening.rb +1 -0
- data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
- data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +2 -0
- data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +3 -0
- data/lib/mongo/operation/create/op_msg.rb +1 -0
- data/lib/mongo/operation/create_index/op_msg.rb +2 -1
- data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
- data/lib/mongo/operation/cursor_command/result.rb +60 -0
- data/lib/mongo/operation/cursor_command.rb +33 -0
- data/lib/mongo/operation/delete/op_msg.rb +1 -0
- data/lib/mongo/operation/drop/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
- data/lib/mongo/operation/find/builder/command.rb +1 -1
- data/lib/mongo/operation/insert/bulk_result.rb +2 -4
- data/lib/mongo/operation/insert/op_msg.rb +1 -0
- data/lib/mongo/operation/result.rb +14 -26
- data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
- data/lib/mongo/operation/shared/executable.rb +2 -10
- data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
- data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
- data/lib/mongo/operation/update/bulk_result.rb +1 -1
- data/lib/mongo/operation/update/op_msg.rb +1 -0
- data/lib/mongo/operation/write_command/op_msg.rb +2 -0
- data/lib/mongo/operation.rb +1 -0
- data/lib/mongo/options/redacted.rb +2 -2
- data/lib/mongo/protocol/compressed.rb +15 -11
- data/lib/mongo/protocol/message.rb +16 -16
- data/lib/mongo/protocol/registry.rb +1 -1
- data/lib/mongo/protocol/serializers.rb +1 -3
- data/lib/mongo/query_cache.rb +3 -5
- data/lib/mongo/server/connection.rb +3 -1
- data/lib/mongo/server/connection_common.rb +9 -1
- data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
- data/lib/mongo/server/connection_pool.rb +79 -18
- data/lib/mongo/server/description/features.rb +2 -2
- data/lib/mongo/server/description.rb +24 -2
- data/lib/mongo/server/monitor/connection.rb +3 -2
- data/lib/mongo/server/monitor.rb +133 -30
- data/lib/mongo/server/pending_connection.rb +1 -0
- data/lib/mongo/server/push_monitor.rb +16 -2
- data/lib/mongo/server.rb +27 -7
- data/lib/mongo/server_selector/base.rb +16 -6
- data/lib/mongo/server_selector/nearest.rb +3 -0
- data/lib/mongo/server_selector/primary.rb +3 -0
- data/lib/mongo/server_selector/primary_preferred.rb +3 -0
- data/lib/mongo/server_selector/secondary.rb +3 -0
- data/lib/mongo/server_selector/secondary_preferred.rb +7 -1
- data/lib/mongo/session.rb +56 -24
- data/lib/mongo/socket/ssl.rb +3 -3
- data/lib/mongo/socket.rb +1 -7
- data/lib/mongo/srv/monitor.rb +11 -2
- data/lib/mongo/srv/resolver.rb +1 -1
- data/lib/mongo/srv/result.rb +5 -3
- data/lib/mongo/timeout.rb +10 -15
- data/lib/mongo/tracing/open_telemetry/command_tracer.rb +43 -2
- data/lib/mongo/uri/options_mapper.rb +21 -9
- data/lib/mongo/uri/srv_protocol.rb +4 -4
- data/lib/mongo/uri.rb +56 -8
- data/lib/mongo/utils.rb +1 -1
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo.rb +1 -0
- metadata +6 -4
- data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
- data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
|
@@ -141,6 +141,13 @@ module Mongo
|
|
|
141
141
|
@pending_connections = Set.new
|
|
142
142
|
@interrupt_connections = []
|
|
143
143
|
|
|
144
|
+
# RUBY-3364: count threads currently blocked on size_cv /
|
|
145
|
+
# max_connecting_cv. When non-zero, a newly-arriving thread must
|
|
146
|
+
# enter the wait queue even if the gate predicate is currently
|
|
147
|
+
# satisfied, to prevent barging past existing waiters.
|
|
148
|
+
@size_waiters = 0
|
|
149
|
+
@max_connecting_waiters = 0
|
|
150
|
+
|
|
144
151
|
# Mutex used for synchronizing access to @available_connections and
|
|
145
152
|
# @checked_out_connections. The pool object is thread-safe, thus
|
|
146
153
|
# all methods that retrieve or modify instance variables generally
|
|
@@ -862,6 +869,22 @@ module Mongo
|
|
|
862
869
|
end
|
|
863
870
|
end
|
|
864
871
|
|
|
872
|
+
# Returns statistics about the internal state of the connection pool,
|
|
873
|
+
# primarily for testing and debugging purposes.
|
|
874
|
+
#
|
|
875
|
+
# @return [ Hash ] The state of the connection pool
|
|
876
|
+
#
|
|
877
|
+
# @api private
|
|
878
|
+
def state
|
|
879
|
+
@lock.synchronize do
|
|
880
|
+
{ available_connections: @available_connections.length,
|
|
881
|
+
checked_out_connections: @checked_out_connections.length,
|
|
882
|
+
pending_connections: @pending_connections.length,
|
|
883
|
+
interrupt_connections: @interrupt_connections.length,
|
|
884
|
+
connection_requests: @connection_requests }
|
|
885
|
+
end
|
|
886
|
+
end
|
|
887
|
+
|
|
865
888
|
private
|
|
866
889
|
|
|
867
890
|
# Returns the next available connection, optionally with given
|
|
@@ -939,7 +962,7 @@ module Mongo
|
|
|
939
962
|
|
|
940
963
|
# Removes and disconnects all stale available connections.
|
|
941
964
|
def remove_stale_connection
|
|
942
|
-
return unless conn = @available_connections.detect { |c| connection_stale_unlocked?(c) }
|
|
965
|
+
return unless (conn = @available_connections.detect { |c| connection_stale_unlocked?(c) })
|
|
943
966
|
|
|
944
967
|
conn.disconnect!(reason: :stale)
|
|
945
968
|
@available_connections.delete(conn)
|
|
@@ -951,7 +974,7 @@ module Mongo
|
|
|
951
974
|
return false if @interrupt_connections.empty?
|
|
952
975
|
|
|
953
976
|
gens = Set.new
|
|
954
|
-
while conn = @interrupt_connections.pop
|
|
977
|
+
while (conn = @interrupt_connections.pop)
|
|
955
978
|
if @checked_out_connections.include?(conn)
|
|
956
979
|
# If the connection has been checked out, mark it as interrupted and it will
|
|
957
980
|
# be disconnected on check in.
|
|
@@ -1035,7 +1058,11 @@ module Mongo
|
|
|
1035
1058
|
@server.unknown!(
|
|
1036
1059
|
generation: e.generation,
|
|
1037
1060
|
service_id: e.service_id,
|
|
1038
|
-
stop_push_monitor: true
|
|
1061
|
+
stop_push_monitor: true,
|
|
1062
|
+
# Only a network error makes the monitor connection suspect and
|
|
1063
|
+
# requires cancelling its check; an auth failure does not.
|
|
1064
|
+
network_error: e.is_a?(Mongo::Error::SocketError) ||
|
|
1065
|
+
e.is_a?(Mongo::Error::SocketTimeoutError)
|
|
1039
1066
|
)
|
|
1040
1067
|
end
|
|
1041
1068
|
connection.disconnect!(reason: :error)
|
|
@@ -1139,12 +1166,6 @@ module Mongo
|
|
|
1139
1166
|
raise Error::ConnectionCheckOutTimeout.new(msg, address: @server.address)
|
|
1140
1167
|
end
|
|
1141
1168
|
|
|
1142
|
-
def raise_check_out_timeout_locked!(connection_global_id)
|
|
1143
|
-
@lock.synchronize do
|
|
1144
|
-
raise_check_out_timeout!(connection_global_id)
|
|
1145
|
-
end
|
|
1146
|
-
end
|
|
1147
|
-
|
|
1148
1169
|
def raise_if_pool_closed!
|
|
1149
1170
|
return unless closed?
|
|
1150
1171
|
|
|
@@ -1239,7 +1260,7 @@ module Mongo
|
|
|
1239
1260
|
# @raise [ Timeout::Error ] If the connection pool is at maximum size
|
|
1240
1261
|
# and remains so for longer than the wait timeout.
|
|
1241
1262
|
def get_connection(pid, connection_global_id)
|
|
1242
|
-
if connection = next_available_connection(connection_global_id)
|
|
1263
|
+
if (connection = next_available_connection(connection_global_id))
|
|
1243
1264
|
return nil unless valid_available_connection?(connection, pid, connection_global_id)
|
|
1244
1265
|
|
|
1245
1266
|
# We've got a connection, so we decrement the number of connection
|
|
@@ -1301,13 +1322,27 @@ module Mongo
|
|
|
1301
1322
|
connection = nil
|
|
1302
1323
|
|
|
1303
1324
|
@lock.synchronize do
|
|
1304
|
-
#
|
|
1305
|
-
#
|
|
1306
|
-
|
|
1325
|
+
# RUBY-3364: if any thread is already waiting for a size slot,
|
|
1326
|
+
# join the queue even when the gate predicate is currently
|
|
1327
|
+
# satisfied. Without this, re-entering threads (those that just
|
|
1328
|
+
# checked a connection back in) barge past existing waiters and
|
|
1329
|
+
# the 195 blocked threads in a 200:5 scenario never wake.
|
|
1330
|
+
# Skip the gate for unlimited pools (max_size == 0) where there
|
|
1331
|
+
# is no size constraint to wait on.
|
|
1332
|
+
must_wait = max_size != 0 && @size_waiters > 0
|
|
1333
|
+
until (max_size == 0 || unavailable_connections < max_size) && !must_wait
|
|
1307
1334
|
wait = deadline - Utils.monotonic_time
|
|
1308
1335
|
raise_check_out_timeout!(connection_global_id) if wait <= 0
|
|
1309
|
-
@
|
|
1336
|
+
@size_waiters += 1
|
|
1337
|
+
begin
|
|
1338
|
+
@size_cv.wait(wait)
|
|
1339
|
+
ensure
|
|
1340
|
+
@size_waiters -= 1
|
|
1341
|
+
end
|
|
1310
1342
|
raise_if_not_ready!
|
|
1343
|
+
# After one wait cycle we have served our "queue tax" and
|
|
1344
|
+
# compete for the slot on the next predicate check.
|
|
1345
|
+
must_wait = false
|
|
1311
1346
|
end
|
|
1312
1347
|
@connection_requests += 1
|
|
1313
1348
|
connection = wait_for_connection(connection_global_id, deadline)
|
|
@@ -1319,8 +1354,17 @@ module Mongo
|
|
|
1319
1354
|
@checked_out_connections << connection
|
|
1320
1355
|
@pending_connections.delete(connection) if @pending_connections.include?(connection)
|
|
1321
1356
|
@max_connecting_cv.signal
|
|
1322
|
-
#
|
|
1323
|
-
#
|
|
1357
|
+
# RUBY-3364: hand off the baton. A waiter that arrived during
|
|
1358
|
+
# our wake-up window (seeing our stale @size_waiters > 0) may be
|
|
1359
|
+
# parked on @size_cv with capacity already available. The
|
|
1360
|
+
# regular check-in path is the only other place that signals
|
|
1361
|
+
# @size_cv, so we wake the next waiter only when the predicate
|
|
1362
|
+
# is actually satisfied for them. Signaling unconditionally
|
|
1363
|
+
# would re-queue a waiter at the back of the FIFO and break
|
|
1364
|
+
# ordering.
|
|
1365
|
+
if @size_waiters > 0 && (max_size == 0 || unavailable_connections < max_size)
|
|
1366
|
+
@size_cv.signal
|
|
1367
|
+
end
|
|
1324
1368
|
end
|
|
1325
1369
|
|
|
1326
1370
|
connection
|
|
@@ -1342,9 +1386,12 @@ module Mongo
|
|
|
1342
1386
|
def wait_for_connection(connection_global_id, deadline)
|
|
1343
1387
|
connection = nil
|
|
1344
1388
|
while connection.nil?
|
|
1389
|
+
# RUBY-3364: as above, yield to any thread already queued for
|
|
1390
|
+
# a max_connecting slot before competing ourselves.
|
|
1391
|
+
must_wait = @max_connecting_waiters > 0
|
|
1345
1392
|
# The second gate to checking out a connection. Make sure 1) there
|
|
1346
1393
|
# exists an available connection and 2) we are under max_connecting.
|
|
1347
|
-
until @available_connections.any? || @pending_connections.length < @max_connecting
|
|
1394
|
+
until (@available_connections.any? || @pending_connections.length < @max_connecting) && !must_wait
|
|
1348
1395
|
wait = deadline - Utils.monotonic_time
|
|
1349
1396
|
if wait <= 0
|
|
1350
1397
|
# We are going to raise a timeout error, so the connection
|
|
@@ -1353,10 +1400,16 @@ module Mongo
|
|
|
1353
1400
|
decrement_connection_requests_and_signal
|
|
1354
1401
|
raise_check_out_timeout!(connection_global_id)
|
|
1355
1402
|
end
|
|
1356
|
-
@
|
|
1403
|
+
@max_connecting_waiters += 1
|
|
1404
|
+
begin
|
|
1405
|
+
@max_connecting_cv.wait(wait)
|
|
1406
|
+
ensure
|
|
1407
|
+
@max_connecting_waiters -= 1
|
|
1408
|
+
end
|
|
1357
1409
|
# We do not need to decrement the connection_requests counter
|
|
1358
1410
|
# or signal here because the pool is not ready yet.
|
|
1359
1411
|
raise_if_not_ready!
|
|
1412
|
+
must_wait = false
|
|
1360
1413
|
end
|
|
1361
1414
|
|
|
1362
1415
|
connection = get_connection(Process.pid, connection_global_id)
|
|
@@ -1370,6 +1423,14 @@ module Mongo
|
|
|
1370
1423
|
raise_check_out_timeout!(connection_global_id)
|
|
1371
1424
|
end
|
|
1372
1425
|
|
|
1426
|
+
# RUBY-3364: hand off the baton for max_connecting_cv. Signal
|
|
1427
|
+
# only if the gate predicate is satisfied for the next waiter, to
|
|
1428
|
+
# avoid re-queuing a waiter at the back of the FIFO.
|
|
1429
|
+
if @max_connecting_waiters > 0 &&
|
|
1430
|
+
(@available_connections.any? || @pending_connections.length < @max_connecting)
|
|
1431
|
+
@max_connecting_cv.signal
|
|
1432
|
+
end
|
|
1433
|
+
|
|
1373
1434
|
connection
|
|
1374
1435
|
end
|
|
1375
1436
|
|
|
@@ -61,7 +61,7 @@ module Mongo
|
|
|
61
61
|
# The wire protocol versions that this version of the driver supports.
|
|
62
62
|
#
|
|
63
63
|
# @since 2.0.0
|
|
64
|
-
DRIVER_WIRE_VERSIONS = 8..
|
|
64
|
+
DRIVER_WIRE_VERSIONS = 8..29
|
|
65
65
|
|
|
66
66
|
# The wire protocol versions that are deprecated in this version of the
|
|
67
67
|
# driver. Support for these versions will be removed in the future.
|
|
@@ -74,7 +74,7 @@ module Mongo
|
|
|
74
74
|
#
|
|
75
75
|
# If there are no currently-deprecated wire versions, this should be
|
|
76
76
|
# set to an empty range (e.g. the EMPTY_RANGE constant).
|
|
77
|
-
DEPRECATED_WIRE_VERSIONS =
|
|
77
|
+
DEPRECATED_WIRE_VERSIONS = 8..8
|
|
78
78
|
|
|
79
79
|
# make sure the deprecated versions are valid
|
|
80
80
|
if DEPRECATED_WIRE_VERSIONS.min && (DRIVER_WIRE_VERSIONS.min > DEPRECATED_WIRE_VERSIONS.max)
|
|
@@ -185,14 +185,35 @@ module Mongo
|
|
|
185
185
|
# @api private
|
|
186
186
|
CONNECTION_ID = 'connectionId'
|
|
187
187
|
|
|
188
|
+
# Constant for reading the modern primary flag from config.
|
|
189
|
+
#
|
|
190
|
+
# @api private
|
|
191
|
+
IS_WRITABLE_PRIMARY = 'isWritablePrimary'
|
|
192
|
+
|
|
193
|
+
# Constant for reading the helloOk capability flag from config.
|
|
194
|
+
#
|
|
195
|
+
# @api private
|
|
196
|
+
HELLO_OK = 'helloOk'
|
|
197
|
+
|
|
188
198
|
# Fields to exclude when comparing two descriptions.
|
|
189
199
|
#
|
|
200
|
+
# The PRIMARY (legacy `ismaster`), IS_WRITABLE_PRIMARY (modern `hello`),
|
|
201
|
+
# and HELLO_OK keys are excluded because the driver does a one-time
|
|
202
|
+
# legacy-`isMaster` to modern-`hello` protocol switch on the initial
|
|
203
|
+
# handshake (per the SDAM Server Monitoring spec). Two responses for the
|
|
204
|
+
# same logical role differ only in which of these keys is populated.
|
|
205
|
+
# The role itself is still differentiated by the SECONDARY flag and the
|
|
206
|
+
# remaining replica-set metadata.
|
|
207
|
+
#
|
|
190
208
|
# @since 2.0.6
|
|
191
209
|
EXCLUDE_FOR_COMPARISON = [ LOCAL_TIME,
|
|
192
210
|
LAST_WRITE,
|
|
193
211
|
OPERATION_TIME,
|
|
194
212
|
Operation::CLUSTER_TIME,
|
|
195
|
-
CONNECTION_ID,
|
|
213
|
+
CONNECTION_ID,
|
|
214
|
+
PRIMARY,
|
|
215
|
+
IS_WRITABLE_PRIMARY,
|
|
216
|
+
HELLO_OK, ].freeze
|
|
196
217
|
|
|
197
218
|
# Instantiate the new server description from the result of the hello
|
|
198
219
|
# command or fabricate a placeholder description for Unknown and
|
|
@@ -258,7 +279,7 @@ module Mongo
|
|
|
258
279
|
"The server at #{address.seed} did not provide a service id in handshake response"
|
|
259
280
|
end
|
|
260
281
|
|
|
261
|
-
fake_service_id = if process_id = topology_version && topology_version['processId']
|
|
282
|
+
fake_service_id = if (process_id = topology_version && topology_version['processId'])
|
|
262
283
|
"process:#{process_id}"
|
|
263
284
|
else
|
|
264
285
|
"fake:#{rand((2**32) - 1) + 1}"
|
|
@@ -866,6 +887,7 @@ module Mongo
|
|
|
866
887
|
def ==(other)
|
|
867
888
|
return false if self.class != other.class
|
|
868
889
|
return false if unknown? || other.unknown?
|
|
890
|
+
return false if server_type != other.server_type
|
|
869
891
|
|
|
870
892
|
(config.keys + other.config.keys).uniq.all? do |k|
|
|
871
893
|
config[k] == other.config[k] || EXCLUDE_FOR_COMPARISON.include?(k)
|
|
@@ -54,9 +54,10 @@ module Mongo
|
|
|
54
54
|
#
|
|
55
55
|
# @since 2.0.0
|
|
56
56
|
def initialize(address, options = {})
|
|
57
|
+
super()
|
|
57
58
|
@address = address
|
|
58
59
|
@options = options.dup.freeze
|
|
59
|
-
raise ArgumentError, 'App metadata is required' unless @app_metadata = options[:app_metadata]
|
|
60
|
+
raise ArgumentError, 'App metadata is required' unless (@app_metadata = options[:app_metadata])
|
|
60
61
|
|
|
61
62
|
@socket = nil
|
|
62
63
|
@pid = Process.pid
|
|
@@ -232,7 +233,7 @@ module Mongo
|
|
|
232
233
|
end
|
|
233
234
|
# compressors must be set to maintain correct compression status
|
|
234
235
|
# in the server description. See RUBY-2427
|
|
235
|
-
if compressors = options[:compressors]
|
|
236
|
+
if (compressors = options[:compressors])
|
|
236
237
|
doc = doc.merge(compression: compressors)
|
|
237
238
|
end
|
|
238
239
|
doc
|
data/lib/mongo/server/monitor.rb
CHANGED
|
@@ -86,14 +86,23 @@ module Mongo
|
|
|
86
86
|
@sdam_mutex = Mutex.new
|
|
87
87
|
@next_earliest_scan = @next_wanted_scan = Time.now
|
|
88
88
|
@update_mutex = Mutex.new
|
|
89
|
+
# Guards reads and writes of @connection so the polling connection can
|
|
90
|
+
# be cancelled from another thread. Per the Server Monitoring spec's
|
|
91
|
+
# cancelCheck pseudocode, the lock is only held long enough to copy or
|
|
92
|
+
# assign the reference - never across the blocking check.
|
|
93
|
+
@connection_lock = Mutex.new
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
# @return [ Server ] server The server that this monitor is monitoring.
|
|
92
97
|
# @api private
|
|
93
98
|
attr_reader :server
|
|
94
99
|
|
|
95
|
-
# @return [ Mongo::Server::Monitor::Connection ]
|
|
96
|
-
|
|
100
|
+
# @return [ Mongo::Server::Monitor::Connection | nil ] The connection to
|
|
101
|
+
# use, read under @connection_lock so callers never observe a stale
|
|
102
|
+
# reference after a concurrent cancel_check! clears it.
|
|
103
|
+
def connection
|
|
104
|
+
@connection_lock.synchronize { @connection }
|
|
105
|
+
end
|
|
97
106
|
|
|
98
107
|
# @return [ Hash ] options The server options.
|
|
99
108
|
attr_reader :options
|
|
@@ -159,7 +168,11 @@ module Mongo
|
|
|
159
168
|
end
|
|
160
169
|
end
|
|
161
170
|
|
|
162
|
-
|
|
171
|
+
# @param [ Mongo::Server::Monitor::Connection ] connection The freshly
|
|
172
|
+
# established monitoring connection. Passed explicitly (rather than read
|
|
173
|
+
# from @connection) so a concurrent cancel_check! cannot nil it out from
|
|
174
|
+
# under us between establishing it and building the PushMonitor.
|
|
175
|
+
def create_push_monitor!(topology_version, connection)
|
|
163
176
|
@update_mutex.synchronize do
|
|
164
177
|
@push_monitor = nil if @push_monitor && !@push_monitor.running?
|
|
165
178
|
|
|
@@ -170,7 +183,7 @@ module Mongo
|
|
|
170
183
|
**Utils.shallow_symbolize_keys(options.merge(
|
|
171
184
|
socket_timeout: heartbeat_interval + connection.socket_timeout,
|
|
172
185
|
app_metadata: options[:push_monitor_app_metadata],
|
|
173
|
-
check_document:
|
|
186
|
+
check_document: connection.check_document
|
|
174
187
|
))
|
|
175
188
|
)
|
|
176
189
|
end
|
|
@@ -185,6 +198,31 @@ module Mongo
|
|
|
185
198
|
end
|
|
186
199
|
end
|
|
187
200
|
|
|
201
|
+
# Cancel the in-progress check and close the monitoring connection.
|
|
202
|
+
#
|
|
203
|
+
# Called when the server is marked Unknown from a network error, per the
|
|
204
|
+
# Server Monitoring spec ("hello or legacy hello Cancellation"). Stops the
|
|
205
|
+
# streaming PushMonitor (interrupting its awaited hello read) and closes
|
|
206
|
+
# the polling connection, so the next check must establish a fresh one
|
|
207
|
+
# rather than re-validating the server over a possibly-dead socket.
|
|
208
|
+
#
|
|
209
|
+
# @api private
|
|
210
|
+
def cancel_check!
|
|
211
|
+
stop_push_monitor!
|
|
212
|
+
|
|
213
|
+
# Copy the connection reference under the lock, then interrupt and close
|
|
214
|
+
# it outside the lock. Closing the socket interrupts any in-progress
|
|
215
|
+
# read on the monitor thread; nil-ing the reference forces the next
|
|
216
|
+
# check to reconnect. The monitor thread is the only writer of a new
|
|
217
|
+
# connection, so it is safe for this thread to clear it.
|
|
218
|
+
connection = @connection_lock.synchronize do
|
|
219
|
+
conn = @connection
|
|
220
|
+
@connection = nil
|
|
221
|
+
conn
|
|
222
|
+
end
|
|
223
|
+
connection&.disconnect!
|
|
224
|
+
end
|
|
225
|
+
|
|
188
226
|
# Perform a check of the server with throttling, and update
|
|
189
227
|
# the server's description and average round trip time.
|
|
190
228
|
#
|
|
@@ -210,28 +248,41 @@ module Mongo
|
|
|
210
248
|
@mutex.synchronize do
|
|
211
249
|
throttle_scan_frequency!
|
|
212
250
|
|
|
251
|
+
# When the streaming protocol is active the PushMonitor is the
|
|
252
|
+
# authoritative SDAM source and this scan only measures RTT on the
|
|
253
|
+
# dedicated connection. Per the Server Monitoring spec, an RTT
|
|
254
|
+
# command MUST NOT publish events or update the topology. Compute
|
|
255
|
+
# this before do_scan, which may (re)connect and change the state.
|
|
256
|
+
rtt_only = rtt_measurement_only?
|
|
257
|
+
|
|
213
258
|
begin
|
|
214
|
-
result = do_scan
|
|
259
|
+
result = do_scan(publish_heartbeat: !rtt_only)
|
|
215
260
|
rescue StandardError => e
|
|
216
|
-
run_sdam_flow({}, scan_error: e)
|
|
261
|
+
run_sdam_flow({}, scan_error: e, rtt_only: rtt_only)
|
|
217
262
|
else
|
|
218
|
-
run_sdam_flow(result)
|
|
263
|
+
run_sdam_flow(result, rtt_only: rtt_only)
|
|
219
264
|
end
|
|
220
265
|
end
|
|
221
266
|
end
|
|
222
267
|
|
|
223
|
-
def run_sdam_flow(result, awaited: false, scan_error: nil)
|
|
268
|
+
def run_sdam_flow(result, awaited: false, scan_error: nil, rtt_only: false)
|
|
224
269
|
@sdam_mutex.synchronize do
|
|
225
270
|
old_description = server.description
|
|
226
271
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
272
|
+
# An RTT-only measurement (streaming protocol active) must not update
|
|
273
|
+
# the topology or publish SDAM events. The RTT it gathered is
|
|
274
|
+
# incorporated into the next streaming-hello description via the
|
|
275
|
+
# shared RTT calculator. The scheduling below still runs so the
|
|
276
|
+
# monitor keeps pacing its checks.
|
|
277
|
+
unless rtt_only
|
|
278
|
+
new_description = Description.new(
|
|
279
|
+
server.address,
|
|
280
|
+
result,
|
|
281
|
+
average_round_trip_time: server.round_trip_time_calculator.average_round_trip_time,
|
|
282
|
+
minimum_round_trip_time: server.round_trip_time_calculator.minimum_round_trip_time
|
|
283
|
+
)
|
|
284
|
+
server.cluster.run_sdam_flow(server.description, new_description, awaited: awaited, scan_error: scan_error)
|
|
285
|
+
end
|
|
235
286
|
|
|
236
287
|
server.description.tap do |new_description|
|
|
237
288
|
unless awaited
|
|
@@ -272,8 +323,12 @@ module Mongo
|
|
|
272
323
|
server.scan_semaphore.signal
|
|
273
324
|
end
|
|
274
325
|
|
|
275
|
-
def do_scan
|
|
276
|
-
|
|
326
|
+
def do_scan(publish_heartbeat: true)
|
|
327
|
+
if publish_heartbeat
|
|
328
|
+
monitoring.publish_heartbeat(server) do
|
|
329
|
+
check
|
|
330
|
+
end
|
|
331
|
+
else
|
|
277
332
|
check
|
|
278
333
|
end
|
|
279
334
|
rescue StandardError => e
|
|
@@ -285,24 +340,53 @@ module Mongo
|
|
|
285
340
|
raise e
|
|
286
341
|
end
|
|
287
342
|
|
|
343
|
+
# Returns whether this scan is only an RTT measurement, which is the case
|
|
344
|
+
# when the streaming protocol is active: a dedicated connection is already
|
|
345
|
+
# established and the PushMonitor is running as the authoritative SDAM
|
|
346
|
+
# source. In the polling protocol there is no running PushMonitor, so the
|
|
347
|
+
# connection-reuse check is a real server check and not RTT-only.
|
|
348
|
+
#
|
|
349
|
+
# @return [ true | false ]
|
|
350
|
+
def rtt_measurement_only?
|
|
351
|
+
return false if connection.nil?
|
|
352
|
+
|
|
353
|
+
# Only suppress the check while the server is in a known state and the
|
|
354
|
+
# PushMonitor is the authoritative streaming source. If the server is
|
|
355
|
+
# Unknown (e.g. an operation error or a streaming failure just marked
|
|
356
|
+
# it so), the polling Monitor must run a full check to recover it
|
|
357
|
+
# rather than waiting for the next streaming response - otherwise the
|
|
358
|
+
# server can stay Unknown long enough to fail server selection.
|
|
359
|
+
return false if server.unknown?
|
|
360
|
+
|
|
361
|
+
pm = push_monitor
|
|
362
|
+
!pm.nil? && pm.running?
|
|
363
|
+
end
|
|
364
|
+
|
|
288
365
|
def check
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
366
|
+
# Snapshot the connection under the lock. A concurrent cancel_check!
|
|
367
|
+
# may nil @connection from another thread; working on a local copy keeps
|
|
368
|
+
# this check consistent, and the guarded writeback below never clobbers
|
|
369
|
+
# a connection the monitor thread did not itself establish.
|
|
370
|
+
connection = self.connection
|
|
371
|
+
|
|
372
|
+
if connection && connection.pid != Process.pid
|
|
373
|
+
log_warn("Detected PID change - Mongo client should have been reconnected (old pid #{connection.pid}, new pid #{Process.pid}")
|
|
374
|
+
connection.disconnect!
|
|
375
|
+
clear_connection(connection)
|
|
376
|
+
connection = nil
|
|
293
377
|
end
|
|
294
378
|
|
|
295
|
-
if
|
|
379
|
+
if connection
|
|
296
380
|
result = server.round_trip_time_calculator.measure do
|
|
297
|
-
doc =
|
|
381
|
+
doc = connection.check_document
|
|
298
382
|
cmd = Protocol::Query.new(
|
|
299
383
|
Database::ADMIN, Database::COMMAND, doc, limit: -1
|
|
300
384
|
)
|
|
301
|
-
message =
|
|
385
|
+
message = connection.dispatch_bytes(cmd.serialize.to_s)
|
|
302
386
|
message.documents.first
|
|
303
387
|
rescue Mongo::Error
|
|
304
|
-
|
|
305
|
-
|
|
388
|
+
connection.disconnect!
|
|
389
|
+
clear_connection(connection)
|
|
306
390
|
raise
|
|
307
391
|
end
|
|
308
392
|
else
|
|
@@ -311,10 +395,13 @@ module Mongo
|
|
|
311
395
|
result = server.round_trip_time_calculator.measure do
|
|
312
396
|
connection.handshake!
|
|
313
397
|
end
|
|
314
|
-
|
|
315
|
-
if tv_doc = result['topologyVersion']
|
|
398
|
+
store_connection(connection)
|
|
399
|
+
if (tv_doc = result['topologyVersion'])
|
|
316
400
|
if streaming_enabled?
|
|
317
|
-
|
|
401
|
+
# Run the instance we just created rather than re-reading the
|
|
402
|
+
# push_monitor getter: a concurrent cancel_check! may have nil'd
|
|
403
|
+
# @push_monitor between the two calls.
|
|
404
|
+
push_monitor = create_push_monitor!(TopologyVersion.new(tv_doc), connection)
|
|
318
405
|
push_monitor.run!
|
|
319
406
|
else
|
|
320
407
|
stop_push_monitor!
|
|
@@ -328,6 +415,22 @@ module Mongo
|
|
|
328
415
|
result
|
|
329
416
|
end
|
|
330
417
|
|
|
418
|
+
# Store a freshly established monitoring connection.
|
|
419
|
+
def store_connection(connection)
|
|
420
|
+
@connection_lock.synchronize do
|
|
421
|
+
@connection = connection
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Clear the monitoring connection, but only if it is still the one passed
|
|
426
|
+
# in. A concurrent cancel_check! may have already cleared or replaced it,
|
|
427
|
+
# in which case we must leave the current connection alone.
|
|
428
|
+
def clear_connection(connection)
|
|
429
|
+
@connection_lock.synchronize do
|
|
430
|
+
@connection = nil if @connection.equal?(connection)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
331
434
|
# @note If the system clock is set to a time in the past, this method
|
|
332
435
|
# can sleep for a very long time.
|
|
333
436
|
def throttle_scan_frequency!
|
|
@@ -72,7 +72,7 @@ module Mongo
|
|
|
72
72
|
# Interrupt any in-progress exhausted hello reads by
|
|
73
73
|
# disconnecting the connection.
|
|
74
74
|
begin
|
|
75
|
-
@connection.
|
|
75
|
+
@connection.interrupt_socket
|
|
76
76
|
rescue StandardError
|
|
77
77
|
nil
|
|
78
78
|
end
|
|
@@ -118,6 +118,13 @@ module Mongo
|
|
|
118
118
|
log_prefix: options[:log_prefix],
|
|
119
119
|
bg_error_backtrace: options[:bg_error_backtrace])
|
|
120
120
|
|
|
121
|
+
# The streaming check is the authoritative SDAM source, so its failure
|
|
122
|
+
# must mark the server Unknown and clear the pool here. While streaming
|
|
123
|
+
# is active the polling Monitor only measures RTT and ignores errors
|
|
124
|
+
# (per the Server Monitoring spec), so it can no longer be relied on to
|
|
125
|
+
# notice the failure.
|
|
126
|
+
monitor.run_sdam_flow({}, scan_error: e, awaited: true)
|
|
127
|
+
|
|
121
128
|
# If a request failed on a connection, stop push monitoring.
|
|
122
129
|
# In case the server is dead we don't want to have two connections
|
|
123
130
|
# trying to connect unsuccessfully at the same time.
|
|
@@ -142,6 +149,13 @@ module Mongo
|
|
|
142
149
|
@server_pushing = false
|
|
143
150
|
connection = PushMonitor::Connection.new(server.address, options)
|
|
144
151
|
connection.connect!
|
|
152
|
+
# Identify the streaming connection to the server with a metadata
|
|
153
|
+
# handshake (carrying the appName) before streaming, mirroring the
|
|
154
|
+
# polling Monitor and the Server Monitoring spec, which establishes
|
|
155
|
+
# the monitoring connection with a handshake before issuing the
|
|
156
|
+
# awaitable hello. Without this, appName-scoped server behaviour
|
|
157
|
+
# never applies to the streaming hello.
|
|
158
|
+
connection.handshake!
|
|
145
159
|
@connection = connection
|
|
146
160
|
end
|
|
147
161
|
end
|
|
@@ -174,7 +188,7 @@ module Mongo
|
|
|
174
188
|
end
|
|
175
189
|
|
|
176
190
|
def read_response
|
|
177
|
-
if timeout = options[:connect_timeout]
|
|
191
|
+
if (timeout = options[:connect_timeout])
|
|
178
192
|
if timeout < 0
|
|
179
193
|
raise Mongo::SocketTimeoutError, 'Requested to read with a negative timeout: '
|
|
180
194
|
elsif timeout > 0
|
data/lib/mongo/server.rb
CHANGED
|
@@ -489,7 +489,8 @@ module Mongo
|
|
|
489
489
|
unknown!(
|
|
490
490
|
generation: e.generation,
|
|
491
491
|
service_id: e.service_id,
|
|
492
|
-
stop_push_monitor: true
|
|
492
|
+
stop_push_monitor: true,
|
|
493
|
+
network_error: true
|
|
493
494
|
)
|
|
494
495
|
end
|
|
495
496
|
raise
|
|
@@ -514,11 +515,13 @@ module Mongo
|
|
|
514
515
|
raise
|
|
515
516
|
rescue Mongo::Error::SocketError, Auth::Unauthorized => e
|
|
516
517
|
# non-timeout network error or auth error, clear the pool and mark the
|
|
517
|
-
# topology as unknown
|
|
518
|
+
# topology as unknown. Only a network error makes the monitor connection
|
|
519
|
+
# suspect; a pure authentication failure must not close it.
|
|
518
520
|
unknown!(
|
|
519
521
|
generation: e.generation,
|
|
520
522
|
service_id: e.service_id,
|
|
521
|
-
stop_push_monitor: true
|
|
523
|
+
stop_push_monitor: true,
|
|
524
|
+
network_error: e.is_a?(Mongo::Error::SocketError)
|
|
522
525
|
)
|
|
523
526
|
raise
|
|
524
527
|
end
|
|
@@ -568,8 +571,15 @@ module Mongo
|
|
|
568
571
|
# on 4.2+ servers).
|
|
569
572
|
# @option options [ TopologyVersion ] :topology_version Topology version
|
|
570
573
|
# of the error response that is causing the server to be marked unknown.
|
|
571
|
-
# @option options [ true | false ] :stop_push_monitor
|
|
572
|
-
#
|
|
574
|
+
# @option options [ true | false ] :stop_push_monitor Set when the server
|
|
575
|
+
# is marked Unknown from a connection-establishment error (e.g. an
|
|
576
|
+
# authentication failure). Stops the streaming PushMonitor associated with
|
|
577
|
+
# the server, if any.
|
|
578
|
+
# @option options [ true | false ] :network_error Set when the server is
|
|
579
|
+
# marked Unknown from a network error. In addition to stopping the
|
|
580
|
+
# PushMonitor, cancels the monitor's in-progress check and closes its
|
|
581
|
+
# connection so the next check must establish a fresh one, per the Server
|
|
582
|
+
# Monitoring spec.
|
|
573
583
|
# @option options [ Object ] :service_id Discard state for the specified
|
|
574
584
|
# service id only.
|
|
575
585
|
#
|
|
@@ -590,7 +600,7 @@ module Mongo
|
|
|
590
600
|
# for example if there haven't been any successful connections yet to
|
|
591
601
|
# this server, but the server can still be marked unknown if one
|
|
592
602
|
# of such connections failed midway through its establishment.
|
|
593
|
-
if service_id = options[:service_id]
|
|
603
|
+
if (service_id = options[:service_id])
|
|
594
604
|
pool&.disconnect!(service_id: service_id)
|
|
595
605
|
end
|
|
596
606
|
return
|
|
@@ -605,7 +615,17 @@ module Mongo
|
|
|
605
615
|
return
|
|
606
616
|
end
|
|
607
617
|
|
|
608
|
-
|
|
618
|
+
if options[:network_error]
|
|
619
|
+
# A network error implies every connection to the server is suspect
|
|
620
|
+
# (Server Monitoring spec, "hello or legacy hello Cancellation"): cancel
|
|
621
|
+
# the monitor's in-progress check and close its connection so the next
|
|
622
|
+
# check must establish a fresh one.
|
|
623
|
+
monitor&.cancel_check!
|
|
624
|
+
elsif options[:stop_push_monitor]
|
|
625
|
+
# Non-network errors (e.g. an authentication failure) only tear down the
|
|
626
|
+
# streaming PushMonitor; the monitor's connection is not suspect.
|
|
627
|
+
monitor&.stop_push_monitor!
|
|
628
|
+
end
|
|
609
629
|
|
|
610
630
|
# SDAM flow will update description on the server without in-place
|
|
611
631
|
# mutations and invoke SDAM transitions as needed.
|