mongo 2.24.0 → 2.24.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cdda79c0346533e626f9458f748cdb003881c93cbe597957706129277872ac0
4
- data.tar.gz: 688197ddc194148b4788526763a706bae2a45e1fef0ac3ed5b1c5b55dd25a781
3
+ metadata.gz: 9359ddba0c1cd025a706f5970893bbdce249c2ab5fbe9d4ce3293a308bb11ade
4
+ data.tar.gz: f88046bc63c0a6b2d23017a3df2ba2cfe72bf6cd7d9565c985e64084bc41147d
5
5
  SHA512:
6
- metadata.gz: f60e23e96c0ba93d06bbec7beb8cbb77420a502b5b64efe3ebafa85475e982d5f8d20c292336a716fa84b4aa6e98791cb2bed09fac1de8578b12b4320a12cdca
7
- data.tar.gz: 34d8751ba7740e254e70397dd204dac01efea33d54d3ee3115cc2d3f6a460c857d625c1f367a672be3f0fc587ac0d09710318fd9011bb4152d110c939f7ec5ae
6
+ metadata.gz: 1ea813993a5e069434299e68eacfb1c79bcbb0161728d703dd20e3047f2699a11640c94067e22c1089e34b56398afb959843eb3d914f110878056c646402c7e8
7
+ data.tar.gz: 837e4866998fc01f8523768fbee6f062a3d0564932f318c27d701e826ed123a4c329df0d74449455afa1146e2de42d7fe54941aaf25e9e9061fc83dd82d87d06
@@ -25,6 +25,10 @@ module Mongo
25
25
  @acknowledged
26
26
  end
27
27
 
28
+ # @return [ Array<String> ] Deduplicated list of "host:port" addresses of
29
+ # the servers that produced this bulk write's operations.
30
+ attr_reader :server_addresses
31
+
28
32
  # Constant for number removed.
29
33
  #
30
34
  # @since 2.1.0
@@ -97,13 +101,17 @@ module Mongo
97
101
  #
98
102
  # @param [ BSON::Document, Hash ] results The results document.
99
103
  # @param [ Boolean ] acknowledged Is the result acknowledged?
104
+ # @param [ Array<String> ] server_addresses Deduplicated "host:port"
105
+ # addresses of the servers that produced the underlying operation
106
+ # results.
100
107
  #
101
108
  # @since 2.1.0
102
109
  #
103
110
  # @api private
104
- def initialize(results, acknowledged)
111
+ def initialize(results, acknowledged, server_addresses = [])
105
112
  @results = results
106
113
  @acknowledged = acknowledged
114
+ @server_addresses = Array(server_addresses).compact.uniq
107
115
  end
108
116
 
109
117
  # Returns the number of documents inserted.
@@ -189,7 +197,7 @@ module Mongo
189
197
  #
190
198
  # @since 2.1.0
191
199
  def validate!
192
- raise Error::BulkWriteError.new(@results) if @results['writeErrors'] || @results['writeConcernErrors']
200
+ raise Error::BulkWriteError.new(@results, server_addresses: @server_addresses) if @results['writeErrors'] || @results['writeConcernErrors']
193
201
 
194
202
  self
195
203
  end
@@ -28,6 +28,10 @@ module Mongo
28
28
  # @return [ Hash ] results The results hash.
29
29
  attr_reader :results
30
30
 
31
+ # @return [ Array<String> ] Deduplicated list of "host:port" addresses of
32
+ # the servers that produced the combined operation results.
33
+ attr_reader :server_addresses
34
+
31
35
  # Create the new result combiner.
32
36
  #
33
37
  # @api private
@@ -39,6 +43,7 @@ module Mongo
39
43
  def initialize
40
44
  @results = {}
41
45
  @count = 0
46
+ @server_addresses = []
42
47
  end
43
48
 
44
49
  # Adds a result to the overall results.
@@ -68,6 +73,8 @@ module Mongo
68
73
  combine_errors!(result)
69
74
  @count += count
70
75
  @acknowledged = result.acknowledged?
76
+ seed = result.connection_description&.address&.seed
77
+ @server_addresses << seed if seed && !@server_addresses.include?(seed)
71
78
  end
72
79
 
73
80
  # Get the final result.
@@ -78,7 +85,7 @@ module Mongo
78
85
  #
79
86
  # @since 2.1.0
80
87
  def result
81
- BulkWrite::Result.new(results, @acknowledged).validate!
88
+ BulkWrite::Result.new(results, @acknowledged, @server_addresses).validate!
82
89
  end
83
90
 
84
91
  private
data/lib/mongo/config.rb CHANGED
@@ -29,6 +29,11 @@ module Mongo
29
29
  # decryption instead of BSON types.
30
30
  option :csfle_convert_to_ruby_types, default: false
31
31
 
32
+ # When this flag is set to true, the (host:port) of the server that produced
33
+ # the error is appended to error messages for OperationFailure and
34
+ # BulkWriteError. See RUBY-3602.
35
+ option :include_server_address_in_errors, default: false
36
+
32
37
  # Set the configuration options.
33
38
  #
34
39
  # @example Set the options.
@@ -34,6 +34,11 @@ module Mongo
34
34
  # @return [ BSON::Document ] result The error result.
35
35
  attr_reader :result
36
36
 
37
+ # @return [ Array<String> ] Deduplicated list of "host:port" addresses of
38
+ # the servers that produced this bulk write error. Empty when no
39
+ # addresses were supplied.
40
+ attr_reader :server_addresses
41
+
37
42
  # Instantiate the new exception.
38
43
  #
39
44
  # @example Instantiate the exception.
@@ -41,10 +46,14 @@ module Mongo
41
46
  #
42
47
  # @param [ Hash ] result A processed response from the server
43
48
  # reporting results of the operation.
49
+ # @param [ Array<String | Mongo::Address | Mongo::Server::Description> ]
50
+ # server_addresses Addresses of the servers that produced this error.
51
+ # Entries are normalized to "host:port" strings.
44
52
  #
45
53
  # @since 2.0.0
46
- def initialize(result)
54
+ def initialize(result, server_addresses: nil)
47
55
  @result = result
56
+ @server_addresses = normalize_server_addresses(server_addresses)
48
57
 
49
58
  # Exception constructor behaves differently for a nil argument and
50
59
  # for no argument. Avoid passing nil explicitly.
@@ -93,8 +102,27 @@ module Mongo
93
102
 
94
103
  fragment = "Multiple errors: #{fragment}" if errors.length > 1
95
104
 
105
+ if Mongo.include_server_address_in_errors && @server_addresses.any?
106
+ fragment = "#{fragment} (on #{@server_addresses.join(', ')})"
107
+ end
108
+
96
109
  fragment
97
110
  end
111
+
112
+ def normalize_server_addresses(value)
113
+ return [] if value.nil?
114
+
115
+ Array(value).filter_map do |entry|
116
+ case entry
117
+ when String then entry
118
+ when Mongo::Address then entry.seed
119
+ when Mongo::Server::Description then entry.address&.seed
120
+ else
121
+ raise ArgumentError,
122
+ "server_addresses entries must be String, Mongo::Address, or Mongo::Server::Description; got #{entry.class}"
123
+ end
124
+ end.uniq
125
+ end
98
126
  end
99
127
  end
100
128
  end
@@ -33,6 +33,9 @@ module Mongo
33
33
  # Error message when hedge is specified for a read preference that does not support it.
34
34
  #
35
35
  # @api private
36
+ #
37
+ # @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
38
+ # be removed in a future version.
36
39
  NO_HEDGE_SUPPORT = 'The hedge option cannot be set for this read preference'
37
40
 
38
41
  # Error message for when the max staleness is not at least twice the heartbeat frequency.
@@ -53,6 +53,10 @@ module Mongo
53
53
  # @api experimental
54
54
  attr_reader :server_message
55
55
 
56
+ # @return [ String | nil ] The address ("host:port") of the server
57
+ # that produced this error, if known.
58
+ attr_reader :server_address
59
+
56
60
  # Error codes and code names that should result in a failing getMore
57
61
  # command on a change stream NOT being resumed.
58
62
  #
@@ -172,6 +176,8 @@ module Mongo
172
176
  # error document.
173
177
  # @option options [ String ] server_message The server-returned
174
178
  # error message parsed from the response.
179
+ # @option options [ nil | String | Mongo::Address | Mongo::Server::Description ]
180
+ # :server_address The address of the server that produced the error.
175
181
  # @option options [ Hash ] :write_concern_error_document The
176
182
  # server-supplied write concern error document, if any.
177
183
  # @option options [ Integer ] :write_concern_error_code Error code for
@@ -185,7 +191,8 @@ module Mongo
185
191
  # @option options [ true | false ] :wtimeout Whether the error is a wtimeout.
186
192
  def initialize(message = nil, result = nil, options = {})
187
193
  @details = retrieve_details(options[:document])
188
- super(append_details(message, @details))
194
+ @server_address = normalize_server_address(options[:server_address])
195
+ super(append_server_address(append_details(message, @details)))
189
196
 
190
197
  @result = result
191
198
  @code = options[:code]
@@ -241,6 +248,38 @@ module Mongo
241
248
 
242
249
  message + " -- #{details.to_json}"
243
250
  end
251
+
252
+ # Append the server address suffix to the message when the
253
+ # Mongo.include_server_address_in_errors flag is enabled and
254
+ # a server address is known.
255
+ #
256
+ # @return [ String | nil ] the message with the suffix appended,
257
+ # or the original message unchanged.
258
+ def append_server_address(message)
259
+ return message unless Mongo.include_server_address_in_errors
260
+ return message if @server_address.nil?
261
+ return "(on #{@server_address})" if message.nil? || message.empty?
262
+
263
+ "#{message} (on #{@server_address})"
264
+ end
265
+
266
+ # Normalize a server_address option into a String "host:port" form.
267
+ #
268
+ # @param [ nil | String | Mongo::Address | Mongo::Server::Description ] value
269
+ #
270
+ # @return [ String | nil ] The normalized address, or nil.
271
+ def normalize_server_address(value)
272
+ case value
273
+ when nil then nil
274
+ when String then value
275
+ when Mongo::Address then value.seed
276
+ when Mongo::Server::Description
277
+ value.address.is_a?(Mongo::Address) ? value.address.seed : nil
278
+ else
279
+ raise ArgumentError,
280
+ "server_address must be nil, String, Mongo::Address, or Mongo::Server::Description; got #{value.class}"
281
+ end
282
+ end
244
283
  end
245
284
 
246
285
  # OperationFailure is the canonical implementor of the
@@ -23,6 +23,8 @@ module Mongo
23
23
  private
24
24
 
25
25
  def log_event(event)
26
+ return if event.previous_description == event.new_description
27
+
26
28
  log_debug(
27
29
  "Server description for #{event.address} changed from " +
28
30
  "'#{event.previous_description.server_type}' to '#{event.new_description.server_type}'#{awaited_indicator(event)}."
@@ -24,6 +24,9 @@ module Mongo
24
24
 
25
25
  def log_event(event)
26
26
  if event.previous_topology.class == event.new_topology.class
27
+ return if event.previous_topology.server_descriptions ==
28
+ event.new_topology.server_descriptions
29
+
27
30
  log_debug(
28
31
  "There was a change in the members of the '#{event.new_topology.display_name}' " +
29
32
  'topology.'
@@ -355,7 +355,8 @@ module Mongo
355
355
  wtimeout: parser.wtimeout,
356
356
  connection_description: connection_description,
357
357
  document: parser.document,
358
- server_message: parser.server_message
358
+ server_message: parser.server_message,
359
+ server_address: connection_description
359
360
  )
360
361
  end
361
362
 
@@ -59,7 +59,7 @@ module Mongo
59
59
  end
60
60
  end
61
61
 
62
- if session.snapshot? && !session.snapshot_timestamp
62
+ if session.snapshot?
63
63
  session.snapshot_timestamp = result.snapshot_timestamp
64
64
  end
65
65
  end
@@ -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
@@ -1301,13 +1308,27 @@ module Mongo
1301
1308
  connection = nil
1302
1309
 
1303
1310
  @lock.synchronize do
1304
- # The first gate to checking out a connection. Make sure the number of
1305
- # unavailable connections is less than the max pool size.
1306
- until max_size == 0 || unavailable_connections < max_size
1311
+ # RUBY-3364: if any thread is already waiting for a size slot,
1312
+ # join the queue even when the gate predicate is currently
1313
+ # satisfied. Without this, re-entering threads (those that just
1314
+ # checked a connection back in) barge past existing waiters and
1315
+ # the 195 blocked threads in a 200:5 scenario never wake.
1316
+ # Skip the gate for unlimited pools (max_size == 0) where there
1317
+ # is no size constraint to wait on.
1318
+ must_wait = max_size != 0 && @size_waiters > 0
1319
+ until (max_size == 0 || unavailable_connections < max_size) && !must_wait
1307
1320
  wait = deadline - Utils.monotonic_time
1308
1321
  raise_check_out_timeout!(connection_global_id) if wait <= 0
1309
- @size_cv.wait(wait)
1322
+ @size_waiters += 1
1323
+ begin
1324
+ @size_cv.wait(wait)
1325
+ ensure
1326
+ @size_waiters -= 1
1327
+ end
1310
1328
  raise_if_not_ready!
1329
+ # After one wait cycle we have served our "queue tax" and
1330
+ # compete for the slot on the next predicate check.
1331
+ must_wait = false
1311
1332
  end
1312
1333
  @connection_requests += 1
1313
1334
  connection = wait_for_connection(connection_global_id, deadline)
@@ -1319,8 +1340,17 @@ module Mongo
1319
1340
  @checked_out_connections << connection
1320
1341
  @pending_connections.delete(connection) if @pending_connections.include?(connection)
1321
1342
  @max_connecting_cv.signal
1322
- # no need to signal size_cv here since the number of unavailable
1323
- # connections is unchanged.
1343
+ # RUBY-3364: hand off the baton. A waiter that arrived during
1344
+ # our wake-up window (seeing our stale @size_waiters > 0) may be
1345
+ # parked on @size_cv with capacity already available. The
1346
+ # regular check-in path is the only other place that signals
1347
+ # @size_cv, so we wake the next waiter only when the predicate
1348
+ # is actually satisfied for them. Signaling unconditionally
1349
+ # would re-queue a waiter at the back of the FIFO and break
1350
+ # ordering.
1351
+ if @size_waiters > 0 && (max_size == 0 || unavailable_connections < max_size)
1352
+ @size_cv.signal
1353
+ end
1324
1354
  end
1325
1355
 
1326
1356
  connection
@@ -1342,9 +1372,12 @@ module Mongo
1342
1372
  def wait_for_connection(connection_global_id, deadline)
1343
1373
  connection = nil
1344
1374
  while connection.nil?
1375
+ # RUBY-3364: as above, yield to any thread already queued for
1376
+ # a max_connecting slot before competing ourselves.
1377
+ must_wait = @max_connecting_waiters > 0
1345
1378
  # The second gate to checking out a connection. Make sure 1) there
1346
1379
  # exists an available connection and 2) we are under max_connecting.
1347
- until @available_connections.any? || @pending_connections.length < @max_connecting
1380
+ until (@available_connections.any? || @pending_connections.length < @max_connecting) && !must_wait
1348
1381
  wait = deadline - Utils.monotonic_time
1349
1382
  if wait <= 0
1350
1383
  # We are going to raise a timeout error, so the connection
@@ -1353,10 +1386,16 @@ module Mongo
1353
1386
  decrement_connection_requests_and_signal
1354
1387
  raise_check_out_timeout!(connection_global_id)
1355
1388
  end
1356
- @max_connecting_cv.wait(wait)
1389
+ @max_connecting_waiters += 1
1390
+ begin
1391
+ @max_connecting_cv.wait(wait)
1392
+ ensure
1393
+ @max_connecting_waiters -= 1
1394
+ end
1357
1395
  # We do not need to decrement the connection_requests counter
1358
1396
  # or signal here because the pool is not ready yet.
1359
1397
  raise_if_not_ready!
1398
+ must_wait = false
1360
1399
  end
1361
1400
 
1362
1401
  connection = get_connection(Process.pid, connection_global_id)
@@ -1370,6 +1409,14 @@ module Mongo
1370
1409
  raise_check_out_timeout!(connection_global_id)
1371
1410
  end
1372
1411
 
1412
+ # RUBY-3364: hand off the baton for max_connecting_cv. Signal
1413
+ # only if the gate predicate is satisfied for the next waiter, to
1414
+ # avoid re-queuing a waiter at the back of the FIFO.
1415
+ if @max_connecting_waiters > 0 &&
1416
+ (@available_connections.any? || @pending_connections.length < @max_connecting)
1417
+ @max_connecting_cv.signal
1418
+ end
1419
+
1373
1420
  connection
1374
1421
  end
1375
1422
 
@@ -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..25
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.
@@ -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, ].freeze
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
@@ -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)
@@ -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.
@@ -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
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.
@@ -936,6 +947,13 @@ module Mongo
936
947
  #
937
948
  # @api private
938
949
  def unpin(connection = nil)
950
+ # Idempotent: if there is no pinned state to clear, do nothing. Nested
951
+ # unpin_maybe handlers (e.g. in BulkWrite#execute_operation wrapping an
952
+ # OpMsg execution that already calls unpin_maybe in its own do_execute)
953
+ # can call this method twice for the same error; checking the connection
954
+ # back into the pool a second time would raise from the pool.
955
+ return if @pinned_server.nil? && @pinned_connection.nil? && @pinned_connection_global_id.nil?
956
+
939
957
  @pinned_server = nil
940
958
  @pinned_connection_global_id = nil
941
959
  conn = connection || @pinned_connection
@@ -1266,8 +1284,23 @@ module Mongo
1266
1284
  @server_session.txn_num
1267
1285
  end
1268
1286
 
1287
+ # @return [ BSON::Timestamp | nil ] The snapshot time for this session.
1288
+ # nil if the session is not a snapshot session, or if it is a snapshot
1289
+ # session for which no :snapshot_time option was provided and no read
1290
+ # has yet captured atClusterTime from the server.
1291
+ attr_reader :snapshot_timestamp
1292
+
1293
+ # Sets the snapshot time for the session. Once set, subsequent
1294
+ # assignments are ignored: snapshotTime is established at most once per
1295
+ # session, either from the :snapshot_time option at construction or from
1296
+ # the atClusterTime returned by the first find/aggregate/distinct
1297
+ # response. This keeps the property effectively read-only for callers,
1298
+ # per the snapshot-sessions spec rationale.
1299
+ #
1269
1300
  # @api private
1270
- attr_accessor :snapshot_timestamp
1301
+ def snapshot_timestamp=(value)
1302
+ @snapshot_timestamp ||= value
1303
+ end
1271
1304
 
1272
1305
  # @return [ Integer | nil ] The deadline for the current transaction, if any.
1273
1306
  # @api private
@@ -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.
data/lib/mongo/version.rb CHANGED
@@ -5,5 +5,5 @@ module Mongo
5
5
  #
6
6
  # Note that this file is automatically updated via `rake candidate:create`.
7
7
  # Manual changes to this file will be overwritten by that rake task.
8
- VERSION = '2.24.0'
8
+ VERSION = '2.24.1'
9
9
  end
data/lib/mongo.rb CHANGED
@@ -101,6 +101,7 @@ module Mongo
101
101
  delegate_option Config, :broken_view_options
102
102
  delegate_option Config, :validate_update_replace
103
103
  delegate_option Config, :csfle_convert_to_ruby_types
104
+ delegate_option Config, :include_server_address_in_errors
104
105
  end
105
106
 
106
107
  # Clears the driver's OCSP response cache.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.24.0
4
+ version: 2.24.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - The MongoDB Ruby Team
@@ -559,7 +559,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
559
559
  - !ruby/object:Gem::Version
560
560
  version: '0'
561
561
  requirements: []
562
- rubygems_version: 4.0.10
562
+ rubygems_version: 4.0.12
563
563
  specification_version: 4
564
564
  summary: Ruby driver for MongoDB
565
565
  test_files: []