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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mongo_console +2 -2
  3. data/lib/mongo/address/validator.rb +1 -1
  4. data/lib/mongo/address.rb +1 -3
  5. data/lib/mongo/auth/aws/conversation.rb +0 -4
  6. data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
  7. data/lib/mongo/auth/base.rb +1 -1
  8. data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
  9. data/lib/mongo/auth/scram_conversation_base.rb +1 -8
  10. data/lib/mongo/auth/stringprep.rb +1 -6
  11. data/lib/mongo/auth/user/view.rb +1 -1
  12. data/lib/mongo/bulk_write/result.rb +10 -2
  13. data/lib/mongo/bulk_write/result_combiner.rb +11 -4
  14. data/lib/mongo/bulk_write.rb +1 -1
  15. data/lib/mongo/client.rb +82 -12
  16. data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
  17. data/lib/mongo/cluster/sdam_flow.rb +4 -4
  18. data/lib/mongo/cluster/topology/base.rb +1 -1
  19. data/lib/mongo/cluster.rb +12 -28
  20. data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
  21. data/lib/mongo/collection/view/aggregation.rb +2 -4
  22. data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
  23. data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
  24. data/lib/mongo/collection/view/change_stream.rb +14 -9
  25. data/lib/mongo/collection/view/map_reduce.rb +2 -11
  26. data/lib/mongo/collection/view/readable.rb +11 -7
  27. data/lib/mongo/collection/view/writable.rb +4 -4
  28. data/lib/mongo/collection/view.rb +8 -4
  29. data/lib/mongo/collection.rb +1 -1
  30. data/lib/mongo/config.rb +5 -0
  31. data/lib/mongo/crypt/auto_encrypter.rb +1 -1
  32. data/lib/mongo/crypt/binary.rb +7 -4
  33. data/lib/mongo/crypt/binding.rb +88 -10
  34. data/lib/mongo/crypt/context.rb +50 -19
  35. data/lib/mongo/crypt/encryption_io.rb +11 -5
  36. data/lib/mongo/crypt/handle.rb +17 -5
  37. data/lib/mongo/crypt/kms/credentials.rb +70 -31
  38. data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
  39. data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
  40. data/lib/mongo/crypt/kms.rb +12 -1
  41. data/lib/mongo/cursor.rb +8 -2
  42. data/lib/mongo/database/cursor_command_view.rb +95 -0
  43. data/lib/mongo/database.rb +144 -18
  44. data/lib/mongo/error/bulk_write_error.rb +29 -1
  45. data/lib/mongo/error/invalid_server_preference.rb +3 -0
  46. data/lib/mongo/error/invalid_uri.rb +5 -1
  47. data/lib/mongo/error/operation_failure.rb +42 -3
  48. data/lib/mongo/error/parser.rb +3 -3
  49. data/lib/mongo/error/sdam_error_detection.rb +2 -3
  50. data/lib/mongo/grid/fs_bucket.rb +27 -21
  51. data/lib/mongo/grid/stream/write.rb +2 -2
  52. data/lib/mongo/index/view.rb +13 -7
  53. data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
  54. data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
  55. data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
  56. data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
  57. data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
  58. data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
  59. data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
  60. data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
  61. data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
  62. data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
  63. data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
  64. data/lib/mongo/monitoring/event/command_failed.rb +1 -0
  65. data/lib/mongo/monitoring/event/command_started.rb +1 -0
  66. data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
  67. data/lib/mongo/monitoring/event/secure.rb +12 -17
  68. data/lib/mongo/monitoring/event/server_closed.rb +1 -0
  69. data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
  70. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
  71. data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
  72. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
  73. data/lib/mongo/monitoring/event/server_opening.rb +1 -0
  74. data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
  75. data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
  76. data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
  77. data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +2 -0
  78. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +3 -0
  79. data/lib/mongo/operation/create/op_msg.rb +1 -0
  80. data/lib/mongo/operation/create_index/op_msg.rb +2 -1
  81. data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
  82. data/lib/mongo/operation/cursor_command/result.rb +60 -0
  83. data/lib/mongo/operation/cursor_command.rb +33 -0
  84. data/lib/mongo/operation/delete/op_msg.rb +1 -0
  85. data/lib/mongo/operation/drop/op_msg.rb +1 -0
  86. data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
  87. data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
  88. data/lib/mongo/operation/find/builder/command.rb +1 -1
  89. data/lib/mongo/operation/insert/bulk_result.rb +2 -4
  90. data/lib/mongo/operation/insert/op_msg.rb +1 -0
  91. data/lib/mongo/operation/result.rb +14 -26
  92. data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
  93. data/lib/mongo/operation/shared/executable.rb +2 -10
  94. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  95. data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
  96. data/lib/mongo/operation/update/bulk_result.rb +1 -1
  97. data/lib/mongo/operation/update/op_msg.rb +1 -0
  98. data/lib/mongo/operation/write_command/op_msg.rb +2 -0
  99. data/lib/mongo/operation.rb +1 -0
  100. data/lib/mongo/options/redacted.rb +2 -2
  101. data/lib/mongo/protocol/compressed.rb +15 -11
  102. data/lib/mongo/protocol/message.rb +16 -16
  103. data/lib/mongo/protocol/registry.rb +1 -1
  104. data/lib/mongo/protocol/serializers.rb +1 -3
  105. data/lib/mongo/query_cache.rb +3 -5
  106. data/lib/mongo/server/connection.rb +3 -1
  107. data/lib/mongo/server/connection_common.rb +9 -1
  108. data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
  109. data/lib/mongo/server/connection_pool.rb +79 -18
  110. data/lib/mongo/server/description/features.rb +2 -2
  111. data/lib/mongo/server/description.rb +24 -2
  112. data/lib/mongo/server/monitor/connection.rb +3 -2
  113. data/lib/mongo/server/monitor.rb +133 -30
  114. data/lib/mongo/server/pending_connection.rb +1 -0
  115. data/lib/mongo/server/push_monitor.rb +16 -2
  116. data/lib/mongo/server.rb +27 -7
  117. data/lib/mongo/server_selector/base.rb +16 -6
  118. data/lib/mongo/server_selector/nearest.rb +3 -0
  119. data/lib/mongo/server_selector/primary.rb +3 -0
  120. data/lib/mongo/server_selector/primary_preferred.rb +3 -0
  121. data/lib/mongo/server_selector/secondary.rb +3 -0
  122. data/lib/mongo/server_selector/secondary_preferred.rb +7 -1
  123. data/lib/mongo/session.rb +56 -24
  124. data/lib/mongo/socket/ssl.rb +3 -3
  125. data/lib/mongo/socket.rb +1 -7
  126. data/lib/mongo/srv/monitor.rb +11 -2
  127. data/lib/mongo/srv/resolver.rb +1 -1
  128. data/lib/mongo/srv/result.rb +5 -3
  129. data/lib/mongo/timeout.rb +10 -15
  130. data/lib/mongo/tracing/open_telemetry/command_tracer.rb +43 -2
  131. data/lib/mongo/uri/options_mapper.rb +21 -9
  132. data/lib/mongo/uri/srv_protocol.rb +4 -4
  133. data/lib/mongo/uri.rb +56 -8
  134. data/lib/mongo/utils.rb +1 -1
  135. data/lib/mongo/version.rb +1 -1
  136. data/lib/mongo.rb +1 -0
  137. metadata +6 -4
  138. data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
  139. data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
@@ -37,6 +37,8 @@ module Mongo
37
37
  # reads on the server. Hedged reads are not enabled by default. When
38
38
  # specifying this option, it must be in the format: { enabled: true },
39
39
  # where the value of the :enabled key is a boolean value.
40
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
41
+ # be removed in a future version.
40
42
  #
41
43
  # @raise [ Error::InvalidServerPreference ] If tag sets are specified
42
44
  # but not allowed.
@@ -51,6 +53,14 @@ module Mongo
51
53
  @hedge = options[:hedge]
52
54
 
53
55
  validate!
56
+
57
+ return if @hedge.nil?
58
+
59
+ Mongo::Deprecations.warn(
60
+ :hedge_read_preference,
61
+ 'The hedge read preference option is deprecated. Hedged reads are ' \
62
+ 'deprecated in MongoDB Server 8.0 and will be removed in a future version.'
63
+ )
54
64
  end
55
65
 
56
66
  # @return [ Hash ] options The options.
@@ -67,6 +77,9 @@ module Mongo
67
77
 
68
78
  # @return [ Hash | nil ] hedge The document specifying whether to enable
69
79
  # hedged reads.
80
+ #
81
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
82
+ # be removed in a future version.
70
83
  attr_reader :hedge
71
84
 
72
85
  # Get the timeout for server selection.
@@ -226,7 +239,7 @@ module Mongo
226
239
 
227
240
  session.unpin unless session.in_transaction?
228
241
 
229
- if server = session.pinned_server
242
+ if (server = session.pinned_server)
230
243
  # Here we assume that a mongos stays in the topology indefinitely.
231
244
  # This will no longer be the case once SRV polling is implemented.
232
245
 
@@ -385,9 +398,7 @@ module Mongo
385
398
  # @api private
386
399
  def candidates(cluster, deprioritized = [])
387
400
  servers = cluster.servers.reject { |s| deprioritized.include?(s) }
388
- if cluster.single?
389
- servers
390
- elsif cluster.sharded?
401
+ if cluster.single? || cluster.sharded?
391
402
  servers
392
403
  elsif cluster.replica_set?
393
404
  select_in_replica_set(servers)
@@ -676,8 +687,7 @@ module Mongo
676
687
  msg = ''
677
688
  dead_monitors = []
678
689
  cluster.servers_list.each do |server|
679
- thread = server.monitor.instance_variable_get(:@thread)
680
- dead_monitors << server if thread.nil? || !thread.alive?
690
+ dead_monitors << server unless server.monitor&.running?
681
691
  end
682
692
  if dead_monitors.any?
683
693
  msg += ". The following servers have dead monitor threads: #{dead_monitors.map(&:summary).join(', ')}"
@@ -59,6 +59,9 @@ module Mongo
59
59
  # Whether the hedge option is allowed to be defined for this server preference.
60
60
  #
61
61
  # @return [ true ] true
62
+ #
63
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
64
+ # be removed in a future version.
62
65
  def hedge_allowed?
63
66
  true
64
67
  end
@@ -59,6 +59,9 @@ module Mongo
59
59
  # Whether the hedge option is allowed to be defined for this server preference.
60
60
  #
61
61
  # @return [ false ] false
62
+ #
63
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
64
+ # be removed in a future version.
62
65
  def hedge_allowed?
63
66
  false
64
67
  end
@@ -59,6 +59,9 @@ module Mongo
59
59
  # Whether the hedge option is allowed to be defined for this server preference.
60
60
  #
61
61
  # @return [ true ] true
62
+ #
63
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
64
+ # be removed in a future version.
62
65
  def hedge_allowed?
63
66
  true
64
67
  end
@@ -59,6 +59,9 @@ module Mongo
59
59
  # Whether the hedge option is allowed to be defined for this server preference.
60
60
  #
61
61
  # @return [ true ] true
62
+ #
63
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
64
+ # be removed in a future version.
62
65
  def hedge_allowed?
63
66
  true
64
67
  end
@@ -59,6 +59,9 @@ module Mongo
59
59
  # Whether the hedge option is allowed to be defined for this server preference.
60
60
  #
61
61
  # @return [ true ] true
62
+ #
63
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
64
+ # be removed in a future version.
62
65
  def hedge_allowed?
63
66
  true
64
67
  end
@@ -98,7 +101,10 @@ module Mongo
98
101
  #
99
102
  # @since 2.0.0
100
103
  def select_in_replica_set(candidates)
101
- near_servers(secondaries(candidates)) + primary(candidates)
104
+ eligible = secondaries(candidates)
105
+ return near_servers(eligible) unless eligible.empty?
106
+
107
+ primary(candidates)
102
108
  end
103
109
 
104
110
  def max_staleness_allowed?
data/lib/mongo/session.rb CHANGED
@@ -74,6 +74,8 @@ module Mongo
74
74
  # and *:nearest*.
75
75
  # @option options [ true | false ] :snapshot Set up the session for
76
76
  # snapshot reads.
77
+ # @option options [ BSON::Timestamp ] :snapshot_time The desired snapshot
78
+ # time for snapshot reads. Only valid when :snapshot is true.
77
79
  #
78
80
  # @since 2.5.0
79
81
  # @api private
@@ -82,6 +84,14 @@ module Mongo
82
84
  raise ArgumentError, ':causal_consistency and :snapshot options cannot be both set on a session'
83
85
  end
84
86
 
87
+ if options[:snapshot_time] && !options[:snapshot]
88
+ raise ArgumentError, ':snapshot_time can only be set when :snapshot is true'
89
+ end
90
+
91
+ if options[:snapshot_time] && !options[:snapshot_time].is_a?(BSON::Timestamp)
92
+ raise ArgumentError, ':snapshot_time must be a BSON::Timestamp'
93
+ end
94
+
85
95
  if options[:implicit]
86
96
  unless server_session.nil?
87
97
  raise ArgumentError, 'Implicit session cannot reference server session during construction'
@@ -104,6 +114,7 @@ module Mongo
104
114
  @with_transaction_deadline = nil
105
115
  @with_transaction_timeout_ms = nil
106
116
  @inside_with_transaction = false
117
+ @snapshot_timestamp = options[:snapshot_time]
107
118
  end
108
119
 
109
120
  # @return [ Hash ] The options for this session.
@@ -448,13 +459,9 @@ module Mongo
448
459
  @inside_with_transaction = true
449
460
  @with_transaction_timeout_ms = options&.dig(:timeout_ms) || @options[:default_timeout_ms] || @client.timeout_ms
450
461
  @with_transaction_deadline = calculate_with_transaction_deadline(options)
451
- deadline = if @with_transaction_deadline
452
- # CSOT enabled, so we have a customer defined deadline.
453
- @with_transaction_deadline
454
- else
455
- # CSOT not enabled, so we use the default deadline, 120 seconds.
456
- Utils.monotonic_time + 120
457
- end
462
+ # When CSOT is enabled we have a customer defined deadline; otherwise
463
+ # fall back to the default deadline, 120 seconds.
464
+ deadline = @with_transaction_deadline || (Utils.monotonic_time + 120)
458
465
  transaction_in_progress = false
459
466
  transaction_attempt = 0
460
467
  last_error = nil
@@ -936,6 +943,13 @@ module Mongo
936
943
  #
937
944
  # @api private
938
945
  def unpin(connection = nil)
946
+ # Idempotent: if there is no pinned state to clear, do nothing. Nested
947
+ # unpin_maybe handlers (e.g. in BulkWrite#execute_operation wrapping an
948
+ # OpMsg execution that already calls unpin_maybe in its own do_execute)
949
+ # can call this method twice for the same error; checking the connection
950
+ # back into the pool a second time would raise from the pool.
951
+ return if @pinned_server.nil? && @pinned_connection.nil? && @pinned_connection_global_id.nil?
952
+
939
953
  @pinned_server = nil
940
954
  @pinned_connection_global_id = nil
941
955
  conn = connection || @pinned_connection
@@ -1036,7 +1050,7 @@ module Mongo
1036
1050
  # even if transaction's read concern is not set.
1037
1051
  # Read concern here is the one sent to the server and may
1038
1052
  # include afterClusterTime.
1039
- if rc = c[:readConcern]
1053
+ if (rc = c[:readConcern])
1040
1054
  rc = rc.dup
1041
1055
  rc.delete(:level)
1042
1056
  end
@@ -1183,7 +1197,7 @@ module Mongo
1183
1197
  def process(result)
1184
1198
  unless implicit?
1185
1199
  set_operation_time(result)
1186
- if cluster_time_doc = result.cluster_time
1200
+ if (cluster_time_doc = result.cluster_time)
1187
1201
  advance_cluster_time(cluster_time_doc)
1188
1202
  end
1189
1203
  end
@@ -1266,8 +1280,23 @@ module Mongo
1266
1280
  @server_session.txn_num
1267
1281
  end
1268
1282
 
1283
+ # @return [ BSON::Timestamp | nil ] The snapshot time for this session.
1284
+ # nil if the session is not a snapshot session, or if it is a snapshot
1285
+ # session for which no :snapshot_time option was provided and no read
1286
+ # has yet captured atClusterTime from the server.
1287
+ attr_reader :snapshot_timestamp
1288
+
1289
+ # Sets the snapshot time for the session. Once set, subsequent
1290
+ # assignments are ignored: snapshotTime is established at most once per
1291
+ # session, either from the :snapshot_time option at construction or from
1292
+ # the atClusterTime returned by the first find/aggregate/distinct
1293
+ # response. This keeps the property effectively read-only for callers,
1294
+ # per the snapshot-sessions spec rationale.
1295
+ #
1269
1296
  # @api private
1270
- attr_accessor :snapshot_timestamp
1297
+ def snapshot_timestamp=(value)
1298
+ @snapshot_timestamp ||= value
1299
+ end
1271
1300
 
1272
1301
  # @return [ Integer | nil ] The deadline for the current transaction, if any.
1273
1302
  # @api private
@@ -1279,8 +1308,6 @@ module Mongo
1279
1308
  @inside_with_transaction
1280
1309
  end
1281
1310
 
1282
- private
1283
-
1284
1311
  # Get the read concern the session will use when starting a transaction.
1285
1312
  #
1286
1313
  # This is a driver style hash with underscore keys.
@@ -1291,11 +1318,24 @@ module Mongo
1291
1318
  # @return [ Hash ] The read concern used for starting transactions.
1292
1319
  #
1293
1320
  # @since 2.9.0
1321
+ # @api private
1294
1322
  def txn_read_concern
1295
1323
  # Read concern is inherited from client but not db or collection.
1296
1324
  txn_options[:read_concern] || @client.read_concern
1297
1325
  end
1298
1326
 
1327
+ # Returns causal consistency document if the last operation time is
1328
+ # known and causal consistency is enabled, otherwise returns nil.
1329
+ #
1330
+ # @api private
1331
+ def causal_consistency_doc
1332
+ return unless operation_time && causal_consistency?
1333
+
1334
+ { afterClusterTime: operation_time }
1335
+ end
1336
+
1337
+ private
1338
+
1299
1339
  def within_states?(*states)
1300
1340
  states.include?(@state)
1301
1341
  end
@@ -1313,14 +1353,6 @@ module Mongo
1313
1353
  (@client.write_concern && @client.write_concern.options)
1314
1354
  end
1315
1355
 
1316
- # Returns causal consistency document if the last operation time is
1317
- # known and causal consistency is enabled, otherwise returns nil.
1318
- def causal_consistency_doc
1319
- return unless operation_time && causal_consistency?
1320
-
1321
- { afterClusterTime: operation_time }
1322
- end
1323
-
1324
1356
  def causal_consistency?
1325
1357
  @causal_consistency ||= if @options.key?(:causal_consistency)
1326
1358
  !!@options[:causal_consistency]
@@ -1358,9 +1390,9 @@ module Mongo
1358
1390
  raise Mongo::Error::InvalidTransactionOperation,
1359
1391
  'timeoutMS cannot be overridden inside a withTransaction callback'
1360
1392
  end
1361
- elsif timeout_ms = opts[:timeout_ms]
1393
+ elsif (timeout_ms = opts[:timeout_ms])
1362
1394
  result[:operation_timeout_ms] = timeout_ms
1363
- elsif default_timeout_ms = options[:default_timeout_ms]
1395
+ elsif (default_timeout_ms = options[:default_timeout_ms])
1364
1396
  result[:operation_timeout_ms] = default_timeout_ms
1365
1397
  end
1366
1398
  end
@@ -1374,9 +1406,9 @@ module Mongo
1374
1406
  Utils.monotonic_time + (timeout / 1000.0)
1375
1407
  end
1376
1408
  }
1377
- if timeout_ms = opts&.dig(:timeout_ms)
1409
+ if (timeout_ms = opts&.dig(:timeout_ms))
1378
1410
  calc.call(timeout_ms)
1379
- elsif default_timeout_ms = @options[:default_timeout_ms]
1411
+ elsif (default_timeout_ms = @options[:default_timeout_ms])
1380
1412
  calc.call(default_timeout_ms)
1381
1413
  elsif @client.timeout_ms
1382
1414
  calc.call(@client.timeout_ms)
@@ -359,7 +359,7 @@ module Mongo
359
359
  if options[:ssl_cert]
360
360
  cert_text = File.read(options[:ssl_cert])
361
361
  cert_object = nil
362
- elsif cert_text = options[:ssl_cert_string]
362
+ elsif (cert_text = options[:ssl_cert_string])
363
363
  cert_object = nil
364
364
  else
365
365
  cert_object = options[:ssl_cert_object]
@@ -421,13 +421,13 @@ module Mongo
421
421
  # https://github.com/jruby/jruby-openssl/issues/176
422
422
  if BSON::Environment.jruby?
423
423
  [ OpenSSL::PKey::RSA, OpenSSL::PKey::DSA ].each do |cls|
424
- return cls.send(:new, *args)
424
+ return cls.new(*args)
425
425
  rescue OpenSSL::PKey::PKeyError
426
426
  # ignore
427
427
  end
428
428
  # Neither RSA nor DSA worked, fall through to trying PKey
429
429
  end
430
- OpenSSL::PKey.send(:read, *args)
430
+ OpenSSL::PKey.read(*args)
431
431
  end
432
432
 
433
433
  def set_cert_verification(context, options)
data/lib/mongo/socket.rb CHANGED
@@ -547,10 +547,6 @@ module Mongo
547
547
  !rv.nil?
548
548
  end
549
549
 
550
- def unix_socket?(sock)
551
- defined?(UNIXSocket) && sock.is_a?(UNIXSocket)
552
- end
553
-
554
550
  DEFAULT_TCP_KEEPINTVL = 10
555
551
 
556
552
  DEFAULT_TCP_KEEPCNT = 9
@@ -590,9 +586,7 @@ module Mongo
590
586
  yield
591
587
  rescue Errno::ETIMEDOUT => e
592
588
  raise Error::SocketTimeoutError, "#{e.class}: #{e} (for #{human_address})"
593
- rescue IOError, SystemCallError, ::SocketError => e
594
- raise Error::SocketError, "#{e.class}: #{e} (for #{human_address})"
595
- rescue OpenSSL::SSL::SSLError => e
589
+ rescue IOError, SystemCallError, ::SocketError, OpenSSL::SSL::SSLError => e
596
590
  raise Error::SocketError, "#{e.class}: #{e} (for #{human_address})"
597
591
  end
598
592
 
@@ -42,10 +42,13 @@ module Mongo
42
42
  # Options to pass through to Resolv::DNS constructor for SRV lookups.
43
43
  def initialize(cluster, **opts)
44
44
  @cluster = cluster
45
- raise ArgumentError, 'SRV URI is required' unless @srv_uri = opts.delete(:srv_uri)
45
+ raise ArgumentError, 'SRV URI is required' unless (@srv_uri = opts.delete(:srv_uri))
46
46
 
47
47
  @options = opts.freeze
48
- @resolver = Srv::Resolver.new(**opts)
48
+ # Per the polling-srv-records spec, mismatched-domain records and empty
49
+ # results MUST be logged and ignored, not raised. Force the polling
50
+ # resolver into log-and-continue mode regardless of caller options.
51
+ @resolver = Srv::Resolver.new(**opts, raise_on_invalid: false)
49
52
  @last_result = @srv_uri.srv_result
50
53
  @stop_semaphore = Semaphore.new
51
54
  end
@@ -82,6 +85,12 @@ module Mongo
82
85
  rescue Resolv::ResolvError => e
83
86
  log_warn("SRV monitor: unable to resolve hostname #{@srv_uri.query_hostname}: #{e.class}: #{e}")
84
87
  return
88
+ rescue Mongo::Error => e
89
+ # Defense in depth: the polling resolver is configured to log-and-continue
90
+ # for mismatched-domain and empty-result cases, but if any Mongo::Error
91
+ # leaks out we MUST NOT let it terminate the SRV monitor thread.
92
+ log_warn("SRV monitor: error resolving hostname #{@srv_uri.query_hostname}: #{e.class}: #{e}")
93
+ return
85
94
  end
86
95
 
87
96
  if last_result.empty?
@@ -142,7 +142,7 @@ module Mongo
142
142
  #
143
143
  # @return [ Boolean ] Whether an error should be raised.
144
144
  def raise_on_invalid?
145
- @raise_on_invalid ||= @options[:raise_on_invalid] || true
145
+ @options.fetch(:raise_on_invalid, true)
146
146
  end
147
147
  end
148
148
  end
@@ -123,9 +123,11 @@ module Mongo
123
123
  raise Error::MismatchedDomain.new(format(MISMATCHED_DOMAINNAME, record_host, srv_host_domain))
124
124
  end
125
125
 
126
- unless (record_host_parts.size > srv_host_domain.size) && (srv_host_domain == record_host_parts[-srv_host_domain.size..-1])
127
- raise Error::MismatchedDomain.new(format(MISMATCHED_DOMAINNAME, record_host, srv_host_domain))
128
- end
126
+ record_matches_domain = (record_host_parts.size > srv_host_domain.size) &&
127
+ (srv_host_domain == record_host_parts[-srv_host_domain.size..-1])
128
+ return if record_matches_domain
129
+
130
+ raise Error::MismatchedDomain.new(format(MISMATCHED_DOMAINNAME, record_host, srv_host_domain))
129
131
  end
130
132
  end
131
133
  end
data/lib/mongo/timeout.rb CHANGED
@@ -18,8 +18,7 @@ module Mongo
18
18
  # @api private
19
19
  module Timeout
20
20
  # A wrapper around Ruby core's Timeout::timeout method that provides
21
- # a standardized API for Ruby versions older and newer than 2.4.0,
22
- # which is when the third argument was introduced.
21
+ # a standardized API for calling it.
23
22
  #
24
23
  # @param [ Numeric ] sec The number of seconds before timeout.
25
24
  # @param [ Class ] klass The exception class to raise on timeout, optional.
@@ -28,19 +27,15 @@ module Mongo
28
27
  # on timeout, optional. When no error message is provided, the default
29
28
  # error message for the exception class is used.
30
29
  def timeout(sec, klass = nil, message = nil, &block)
31
- if message && RUBY_VERSION < '2.94.0'
32
- begin
33
- ::Timeout.timeout(sec, &block)
34
- rescue ::Timeout::Error
35
- raise klass, message
36
- end
37
- else
38
- # Jruby Timeout::timeout method does not support passing nil arguments.
39
- # Remove the nil arguments before passing them along to the core
40
- # Timeout::timeout method.
41
- optional_args = [ klass, message ].compact
42
- ::Timeout.timeout(sec, *optional_args, &block)
43
- end
30
+ # provide a default error class if the message is present; otherwise,
31
+ # the message will be interpreted as the error class, if klass is nil
32
+ klass ||= ::Timeout::Error if message
33
+
34
+ # Jruby Timeout::timeout method does not support passing nil arguments.
35
+ # Remove the nil arguments before passing them along to the core
36
+ # Timeout::timeout method.
37
+ optional_args = [ klass, message ].compact
38
+ ::Timeout.timeout(sec, *optional_args, &block)
44
39
  end
45
40
  module_function :timeout
46
41
  end
@@ -21,6 +21,15 @@ module Mongo
21
21
  #
22
22
  # @api private
23
23
  class CommandTracer
24
+ include Mongo::Monitoring::Event::Secure
25
+
26
+ # Commands for which a span MUST NOT be created. The OpenTelemetry spec
27
+ # requires drivers to skip command spans for sensitive commands listed in
28
+ # the Command Logging and Monitoring spec. We additionally skip hello /
29
+ # legacy hello in all forms — these are handshake/heartbeat traffic and
30
+ # would only add noise to traces.
31
+ HELLO_COMMANDS = %w[hello ismaster isMaster].freeze
32
+
24
33
  # Initializes a new CommandTracer.
25
34
  #
26
35
  # @param otel_tracer [ OpenTelemetry::Trace::Tracer ] the OpenTelemetry tracer.
@@ -57,6 +66,8 @@ module Mongo
57
66
  # @return [ Object ] the result of the command.
58
67
  # rubocop:disable Lint/RescueException
59
68
  def trace_command(message, _operation_context, connection)
69
+ return yield if skip_tracing?(message)
70
+
60
71
  # Commands should always be nested under their operation span, not directly under
61
72
  # the transaction span. Don't pass with_parent to use automatic parent resolution
62
73
  # from the currently active span (the operation span).
@@ -76,6 +87,22 @@ module Mongo
76
87
 
77
88
  private
78
89
 
90
+ # Determines whether the command must not be traced. Sensitive auth
91
+ # commands carry credentials in their payloads (SCRAM proofs, cleartext
92
+ # passwords, etc.) and the OpenTelemetry spec requires drivers to skip
93
+ # command spans for them. Hello / legacy hello are also skipped to keep
94
+ # handshake traffic out of traces.
95
+ #
96
+ # @param message [ Mongo::Protocol::Message ] the command message.
97
+ #
98
+ # @return [ Boolean ] true when no command span should be created.
99
+ def skip_tracing?(message)
100
+ name = command_name(message)
101
+ return true if HELLO_COMMANDS.include?(name)
102
+
103
+ sensitive?(command_name: name, document: message.documents.first)
104
+ end
105
+
79
106
  # Creates a span for a command.
80
107
  #
81
108
  # @param message [ Mongo::Protocol::Message ] the command message.
@@ -179,9 +206,23 @@ module Mongo
179
206
  # @param _context [ OpenTelemetry::Context ] the context (unused).
180
207
  # @param span [ OpenTelemetry::Trace::Span ] the current span.
181
208
  def process_cursor_context(result, _cursor_id, _context, span)
182
- return unless result.has_cursor_id? && result.cursor_id.positive?
209
+ cursor_id = normalize_cursor_id(result.cursor_id)
210
+ return unless cursor_id.positive?
211
+
212
+ span.set_attribute('db.mongodb.cursor_id', cursor_id)
213
+ end
183
214
 
184
- span.set_attribute('db.mongodb.cursor_id', result.cursor_id)
215
+ # Normalizes a cursor id to a plain Integer.
216
+ #
217
+ # OP_MSG replies deserialized in :bson mode expose the cursor id as a
218
+ # BSON::Int64, which does not implement Numeric#positive? and is not a
219
+ # valid OpenTelemetry attribute type.
220
+ #
221
+ # @param cursor_id [ Integer | BSON::Int64 ] the raw cursor id.
222
+ #
223
+ # @return [ Integer ] the cursor id as an Integer.
224
+ def normalize_cursor_id(cursor_id)
225
+ cursor_id.is_a?(BSON::Int64) ? cursor_id.value : cursor_id
185
226
  end
186
227
 
187
228
  # Records error status code if the command failed.
@@ -87,7 +87,7 @@ module Mongo
87
87
  strategy = URI_OPTION_MAP[key.downcase]
88
88
  if strategy.nil?
89
89
  log_warn("Unsupported URI option '#{key}' on URI '#{@string}'. It will be ignored.")
90
- return
90
+ next
91
91
  end
92
92
 
93
93
  group = strategy[:group]
@@ -125,7 +125,7 @@ module Mongo
125
125
  end
126
126
  next if v.nil?
127
127
 
128
- if type = spec[:type]
128
+ if (type = spec[:type])
129
129
  v = send("revert_#{type}", v)
130
130
  end
131
131
  canonical_key = URI_OPTION_CANONICAL_NAMES[uri_key]
@@ -163,7 +163,7 @@ module Mongo
163
163
  end
164
164
  next if v.nil?
165
165
 
166
- if type = spec[:type]
166
+ if (type = spec[:type])
167
167
  v = send("stringify_#{type}", v)
168
168
  end
169
169
  canonical_key = URI_OPTION_CANONICAL_NAMES[uri_key]
@@ -354,13 +354,25 @@ module Mongo
354
354
 
355
355
  # Converts the value into a boolean and returns it wrapped in an array.
356
356
  #
357
- # @param [ String ] name Name of the URI option being processed.
357
+ # tls and ssl are the only repeated_bool options. An invalid value is
358
+ # preserved as a nil element rather than warned-and-dropped, so that
359
+ # URI#validate_uri_options! can detect it and raise instead of silently
360
+ # falling back to a plaintext connection (RUBY-3832).
361
+ #
362
+ # @param [ String ] _name Name of the URI option being processed.
358
363
  # @param [ String ] value URI option value.
359
364
  #
360
- # @return [ Array<true | false> | nil ] The boolean value parsed and wraped
361
- # in an array.
362
- def convert_repeated_bool(name, value)
363
- [ convert_bool(name, value) ]
365
+ # @return [ Array<true | false | nil> ] The boolean value parsed and
366
+ # wrapped in an array.
367
+ def convert_repeated_bool(_name, value)
368
+ case value
369
+ when true, 'true', 'TRUE'
370
+ [ true ]
371
+ when false, 'false', 'FALSE'
372
+ [ false ]
373
+ else
374
+ [ nil ]
375
+ end
364
376
  end
365
377
 
366
378
  # Reverts a repeated boolean type.
@@ -379,7 +391,7 @@ module Mongo
379
391
  # @return [ Array<true | false> | nil ] The string.
380
392
  def stringify_repeated_bool(value)
381
393
  rep = revert_repeated_bool(value)
382
- if rep&.is_a?(Array)
394
+ if rep.is_a?(Array)
383
395
  rep.join(',')
384
396
  else
385
397
  rep
@@ -117,14 +117,14 @@ module Mongo
117
117
  raise Error::InvalidURI.new(@string, details, FORMAT)
118
118
  end
119
119
 
120
- # Gets the SRV resolver.
121
- # If domain verification fails or no SRV records are found,
122
- # an error must not be raised per the spec; instead, a warning is logged.
120
+ # Gets the SRV resolver used for initial URI parsing.
121
+ # Per the Initial DNS Seedlist Discovery spec, the driver MUST raise an
122
+ # error if domain verification fails or no SRV records are found, so
123
+ # raise_on_invalid is left at its default of true.
123
124
  #
124
125
  # @return [ Mongo::Srv::Resolver ]
125
126
  def resolver
126
127
  @resolver ||= Srv::Resolver.new(
127
- raise_on_invalid: false,
128
128
  resolv_options: options[:resolv_options],
129
129
  timeout: options[:connect_timeout]
130
130
  )