mongodb 7.1.0 → 7.2.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 (102) hide show
  1. package/README.md +11 -0
  2. package/lib/bson.js +26 -5
  3. package/lib/bson.js.map +1 -1
  4. package/lib/change_stream.js +4 -0
  5. package/lib/change_stream.js.map +1 -1
  6. package/lib/client-side-encryption/auto_encrypter.js +19 -10
  7. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  8. package/lib/client-side-encryption/client_encryption.js +1 -3
  9. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  10. package/lib/cmap/auth/aws4.js +4 -4
  11. package/lib/cmap/auth/aws4.js.map +1 -1
  12. package/lib/cmap/auth/gssapi.js +3 -6
  13. package/lib/cmap/auth/gssapi.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_aws.js +3 -2
  15. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  16. package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js +3 -3
  17. package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js.map +1 -1
  18. package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js +3 -3
  19. package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js.map +1 -1
  20. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +3 -3
  21. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -1
  22. package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js +3 -3
  23. package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -1
  24. package/lib/cmap/auth/mongodb_oidc.js +4 -4
  25. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  26. package/lib/cmap/auth/plain.js +1 -1
  27. package/lib/cmap/auth/plain.js.map +1 -1
  28. package/lib/cmap/auth/scram.js +53 -40
  29. package/lib/cmap/auth/scram.js.map +1 -1
  30. package/lib/cmap/commands.js +46 -39
  31. package/lib/cmap/commands.js.map +1 -1
  32. package/lib/cmap/connect.js +19 -2
  33. package/lib/cmap/connect.js.map +1 -1
  34. package/lib/cmap/connection.js +5 -2
  35. package/lib/cmap/connection.js.map +1 -1
  36. package/lib/cmap/handshake/client_metadata.js +3 -4
  37. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  38. package/lib/cmap/wire_protocol/compression.js +8 -7
  39. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  40. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  41. package/lib/cmap/wire_protocol/on_demand/document.js +9 -9
  42. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  43. package/lib/connection_string.js +21 -5
  44. package/lib/connection_string.js.map +1 -1
  45. package/lib/gridfs/download.js +2 -1
  46. package/lib/gridfs/download.js.map +1 -1
  47. package/lib/gridfs/upload.js +7 -7
  48. package/lib/gridfs/upload.js.map +1 -1
  49. package/lib/mongo_client.js.map +1 -1
  50. package/lib/operations/execute_operation.js +114 -41
  51. package/lib/operations/execute_operation.js.map +1 -1
  52. package/lib/operations/operation.js +1 -0
  53. package/lib/operations/operation.js.map +1 -1
  54. package/lib/runtime_adapters.js +32 -0
  55. package/lib/runtime_adapters.js.map +1 -0
  56. package/lib/sdam/srv_polling.js +1 -1
  57. package/lib/sdam/srv_polling.js.map +1 -1
  58. package/lib/sdam/topology.js +4 -2
  59. package/lib/sdam/topology.js.map +1 -1
  60. package/lib/sessions.js +124 -79
  61. package/lib/sessions.js.map +1 -1
  62. package/lib/utils.js +28 -36
  63. package/lib/utils.js.map +1 -1
  64. package/mongodb.d.ts +45 -2
  65. package/package.json +30 -21
  66. package/src/bson.ts +28 -5
  67. package/src/change_stream.ts +5 -0
  68. package/src/client-side-encryption/auto_encrypter.ts +17 -11
  69. package/src/client-side-encryption/client_encryption.ts +1 -3
  70. package/src/cmap/auth/auth_provider.ts +1 -1
  71. package/src/cmap/auth/aws4.ts +5 -5
  72. package/src/cmap/auth/gssapi.ts +9 -6
  73. package/src/cmap/auth/mongodb_aws.ts +2 -2
  74. package/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts +1 -1
  75. package/src/cmap/auth/mongodb_oidc/gcp_machine_workflow.ts +1 -1
  76. package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +1 -1
  77. package/src/cmap/auth/mongodb_oidc/token_machine_workflow.ts +1 -1
  78. package/src/cmap/auth/mongodb_oidc.ts +4 -4
  79. package/src/cmap/auth/plain.ts +2 -2
  80. package/src/cmap/auth/scram.ts +82 -55
  81. package/src/cmap/commands.ts +70 -51
  82. package/src/cmap/connect.ts +21 -1
  83. package/src/cmap/connection.ts +11 -4
  84. package/src/cmap/handshake/client_metadata.ts +6 -6
  85. package/src/cmap/wire_protocol/compression.ts +18 -14
  86. package/src/cmap/wire_protocol/on_data.ts +5 -5
  87. package/src/cmap/wire_protocol/on_demand/document.ts +12 -14
  88. package/src/connection_string.ts +26 -8
  89. package/src/deps.ts +4 -4
  90. package/src/gridfs/download.ts +2 -2
  91. package/src/gridfs/upload.ts +13 -12
  92. package/src/index.ts +1 -0
  93. package/src/mongo_client.ts +24 -0
  94. package/src/operations/client_bulk_write/command_builder.ts +1 -1
  95. package/src/operations/execute_operation.ts +146 -45
  96. package/src/operations/operation.ts +8 -0
  97. package/src/runtime_adapters.ts +64 -0
  98. package/src/sdam/srv_polling.ts +1 -1
  99. package/src/sdam/topology.ts +10 -7
  100. package/src/sessions.ts +140 -96
  101. package/src/utils.ts +40 -45
  102. package/tsconfig.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { setTimeout } from 'timers/promises';
2
+
1
3
  import { MIN_SUPPORTED_SNAPSHOT_READS_WIRE_VERSION } from '../cmap/wire_protocol/constants';
2
4
  import {
3
5
  isRetryableReadError,
@@ -17,6 +19,7 @@ import {
17
19
  } from '../error';
18
20
  import type { MongoClient } from '../mongo_client';
19
21
  import { ReadPreference } from '../read_preference';
22
+ import { TopologyType } from '../sdam/common';
20
23
  import {
21
24
  DeprioritizedServers,
22
25
  sameServerSelector,
@@ -29,6 +32,7 @@ import { TimeoutContext } from '../timeout';
29
32
  import { abortable, maxWireVersion, supportsRetryableWrites } from '../utils';
30
33
  import { AggregateOperation } from './aggregate';
31
34
  import { AbstractOperation, Aspect } from './operation';
35
+ import { RunCommandOperation } from './run_command';
32
36
 
33
37
  const MMAPv1_RETRY_WRITES_ERROR_CODE = MONGODB_ERROR_CODES.IllegalOperation;
34
38
  const MMAPv1_RETRY_WRITES_ERROR_MESSAGE =
@@ -50,7 +54,7 @@ type ResultTypeFromOperation<TOperation extends AbstractOperation> = ReturnType<
50
54
  * The expectation is that this function:
51
55
  * - Connects the MongoClient if it has not already been connected, see {@link autoConnect}
52
56
  * - Creates a session if none is provided and cleans up the session it creates
53
- * - Tries an operation and retries under certain conditions, see {@link tryOperation}
57
+ * - Tries an operation and retries under certain conditions, see {@link executeOperationWithRetries}
54
58
  *
55
59
  * @typeParam T - The operation's type
56
60
  * @typeParam TResult - The type of the operation's result, calculated from T
@@ -120,7 +124,7 @@ export async function executeOperation<
120
124
  });
121
125
 
122
126
  try {
123
- return await tryOperation(operation, {
127
+ return await executeOperationWithRetries(operation, {
124
128
  topology,
125
129
  timeoutContext,
126
130
  session,
@@ -165,6 +169,10 @@ type RetryOptions = {
165
169
  topology: Topology;
166
170
  timeoutContext: TimeoutContext;
167
171
  };
172
+ /** @internal The base backoff duration in milliseconds */
173
+ const BASE_BACKOFF_MS = 100;
174
+ /** @internal The maximum backoff duration in milliseconds */
175
+ const MAX_BACKOFF_MS = 10_000;
168
176
 
169
177
  /**
170
178
  * Executes an operation and retries as appropriate
@@ -183,8 +191,11 @@ type RetryOptions = {
183
191
  * @typeParam TResult - The type of the operation's result, calculated from T
184
192
  *
185
193
  * @param operation - The operation to execute
186
- * */
187
- async function tryOperation<T extends AbstractOperation, TResult = ResultTypeFromOperation<T>>(
194
+ */
195
+ async function executeOperationWithRetries<
196
+ T extends AbstractOperation,
197
+ TResult = ResultTypeFromOperation<T>
198
+ >(
188
199
  operation: T,
189
200
  { topology, timeoutContext, session, readPreference }: RetryOptions
190
201
  ): Promise<TResult> {
@@ -233,33 +244,70 @@ async function tryOperation<T extends AbstractOperation, TResult = ResultTypeFro
233
244
  session.incrementTransactionNumber();
234
245
  }
235
246
 
236
- const maxTries = willRetry ? (timeoutContext.csotEnabled() ? Infinity : 2) : 1;
237
- let previousOperationError: MongoError | undefined;
238
247
  const deprioritizedServers = new DeprioritizedServers();
239
248
 
240
- for (let tries = 0; tries < maxTries; tries++) {
241
- if (previousOperationError) {
242
- if (hasWriteAspect && previousOperationError.code === MMAPv1_RETRY_WRITES_ERROR_CODE) {
249
+ let maxAttempts =
250
+ typeof operation.maxAttempts === 'number'
251
+ ? operation.maxAttempts
252
+ : willRetry
253
+ ? timeoutContext.csotEnabled()
254
+ ? Infinity
255
+ : 2
256
+ : 1;
257
+
258
+ let error: MongoError | null = null;
259
+
260
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
261
+ operation.attemptsMade = attempt + 1;
262
+ operation.server = server;
263
+
264
+ try {
265
+ try {
266
+ const result = await server.command(operation, timeoutContext);
267
+ return operation.handleOk(result);
268
+ } catch (error) {
269
+ return operation.handleError(error);
270
+ }
271
+ } catch (operationError) {
272
+ // Should never happen but if it does - propagate the error.
273
+ if (!(operationError instanceof MongoError)) throw operationError;
274
+
275
+ // Preserve the original error once a write has been performed.
276
+ // Only update to the latest error if no writes were performed.
277
+ if (error == null) {
278
+ error = operationError;
279
+ } else {
280
+ if (!operationError.hasErrorLabel(MongoErrorLabel.NoWritesPerformed)) {
281
+ error = operationError;
282
+ }
283
+ }
284
+
285
+ // Reset timeouts
286
+ timeoutContext.clear();
287
+
288
+ if (hasWriteAspect && operationError.code === MMAPv1_RETRY_WRITES_ERROR_CODE) {
243
289
  throw new MongoServerError({
244
290
  message: MMAPv1_RETRY_WRITES_ERROR_MESSAGE,
245
291
  errmsg: MMAPv1_RETRY_WRITES_ERROR_MESSAGE,
246
- originalError: previousOperationError
292
+ originalError: operationError
247
293
  });
248
294
  }
249
295
 
250
- if (operation.hasAspect(Aspect.COMMAND_BATCHING) && !operation.canRetryWrite) {
251
- throw previousOperationError;
296
+ if (!canRetry(operation, operationError)) {
297
+ throw error;
252
298
  }
253
299
 
254
- if (hasWriteAspect && !isRetryableWriteError(previousOperationError))
255
- throw previousOperationError;
300
+ if (operationError.hasErrorLabel(MongoErrorLabel.SystemOverloadedError)) {
301
+ const maxOverloadAttempts = topology.s.options.maxAdaptiveRetries + 1;
302
+ maxAttempts = Math.min(maxOverloadAttempts, operation.maxAttempts ?? maxOverloadAttempts);
303
+ }
256
304
 
257
- if (hasReadAspect && !isRetryableReadError(previousOperationError)) {
258
- throw previousOperationError;
305
+ if (attempt + 1 >= maxAttempts) {
306
+ throw error;
259
307
  }
260
308
 
261
309
  if (
262
- previousOperationError instanceof MongoNetworkError &&
310
+ operationError instanceof MongoNetworkError &&
263
311
  operation.hasAspect(Aspect.CURSOR_CREATING) &&
264
312
  session != null &&
265
313
  session.isPinned &&
@@ -268,6 +316,35 @@ async function tryOperation<T extends AbstractOperation, TResult = ResultTypeFro
268
316
  session.unpin({ force: true, forceClear: true });
269
317
  }
270
318
 
319
+ if (
320
+ operationError.hasErrorLabel(MongoErrorLabel.SystemOverloadedError) &&
321
+ operation.hasAspect(Aspect.CURSOR_CREATING) &&
322
+ session != null &&
323
+ session.isPinned &&
324
+ !session.inTransaction()
325
+ ) {
326
+ session.unpin({ force: true });
327
+ }
328
+
329
+ if (operationError.hasErrorLabel(MongoErrorLabel.SystemOverloadedError)) {
330
+ const backoffMS = Math.random() * Math.min(MAX_BACKOFF_MS, BASE_BACKOFF_MS * 2 ** attempt);
331
+
332
+ // if the backoff would exhaust the CSOT timeout, short-circuit.
333
+ if (timeoutContext.csotEnabled() && backoffMS > timeoutContext.remainingTimeMS) {
334
+ throw error;
335
+ }
336
+
337
+ await setTimeout(backoffMS);
338
+ }
339
+
340
+ if (
341
+ topology.description.type === TopologyType.Sharded ||
342
+ (operationError.hasErrorLabel(MongoErrorLabel.SystemOverloadedError) &&
343
+ topology.s.options.enableOverloadRetargeting)
344
+ ) {
345
+ deprioritizedServers.add(server.description);
346
+ }
347
+
271
348
  server = await topology.selectServer(selector, {
272
349
  session,
273
350
  operationName: operation.commandName,
@@ -275,45 +352,69 @@ async function tryOperation<T extends AbstractOperation, TResult = ResultTypeFro
275
352
  signal: operation.options.signal
276
353
  });
277
354
 
278
- if (hasWriteAspect && !supportsRetryableWrites(server)) {
355
+ if (
356
+ hasWriteAspect &&
357
+ !supportsRetryableWrites(server) &&
358
+ !operationError.hasErrorLabel(MongoErrorLabel.SystemOverloadedError)
359
+ ) {
279
360
  throw new MongoUnexpectedServerResponseError(
280
361
  'Selected server does not support retryable writes'
281
362
  );
282
363
  }
283
- }
284
-
285
- operation.server = server;
286
364
 
287
- try {
288
- // If tries > 0 and we are command batching we need to reset the batch.
289
- if (tries > 0 && operation.hasAspect(Aspect.COMMAND_BATCHING)) {
365
+ // Batched operations must reset the batch before retry,
366
+ // otherwise building a command will build the _next_ batch, not the current batch.
367
+ if (operation.hasAspect(Aspect.COMMAND_BATCHING)) {
290
368
  operation.resetBatch();
291
369
  }
370
+ }
371
+ }
292
372
 
293
- try {
294
- const result = await server.command(operation, timeoutContext);
295
- return operation.handleOk(result);
296
- } catch (error) {
297
- return operation.handleError(error);
373
+ throw (
374
+ error ??
375
+ new MongoRuntimeError(
376
+ 'Should never happen: operation execution loop terminated but no error was recorded.'
377
+ )
378
+ );
379
+
380
+ function canRetry(operation: AbstractOperation, error: MongoError) {
381
+ // SystemOverloadedError is retryable, but must respect retryReads/retryWrites settings
382
+ // Check topology options directly (not operation.canRetryRead/Write) because backpressure
383
+ // expands retry support beyond traditional retryable reads/writes
384
+ // NOTE: Unlike traditional retries, backpressure retries ARE allowed inside transactions
385
+ if (
386
+ error.hasErrorLabel(MongoErrorLabel.SystemOverloadedError) &&
387
+ error.hasErrorLabel(MongoErrorLabel.RetryableError)
388
+ ) {
389
+ // runCommand requires BOTH retryReads and retryWrites to be enabled (per spec step 2.4)
390
+ if (operation instanceof RunCommandOperation) {
391
+ return topology.s.options.retryReads && topology.s.options.retryWrites;
298
392
  }
299
- } catch (operationError) {
300
- if (!(operationError instanceof MongoError)) throw operationError;
301
- if (
302
- previousOperationError != null &&
303
- operationError.hasErrorLabel(MongoErrorLabel.NoWritesPerformed)
304
- ) {
305
- throw previousOperationError;
393
+
394
+ // Write-stage aggregates ($out/$merge) require retryWrites
395
+ if (operation instanceof AggregateOperation && operation.hasWriteStage) {
396
+ return topology.s.options.retryWrites;
306
397
  }
307
- deprioritizedServers.add(server.description);
308
- previousOperationError = operationError;
309
398
 
310
- // Reset timeouts
311
- timeoutContext.clear();
399
+ // For other operations, check if retries are enabled based on operation type
400
+ const canRetryAsRead = hasReadAspect && topology.s.options.retryReads;
401
+ const canRetryAsWrite = hasWriteAspect && topology.s.options.retryWrites;
402
+ return canRetryAsRead || canRetryAsWrite;
312
403
  }
313
- }
314
404
 
315
- throw (
316
- previousOperationError ??
317
- new MongoRuntimeError('Tried to propagate retryability error, but no error was found.')
318
- );
405
+ // run command is only retryable if we get retryable overload errors
406
+ if (operation instanceof RunCommandOperation) {
407
+ return false;
408
+ }
409
+
410
+ // batch operations are only retryable if the batch is retryable
411
+ if (operation.hasAspect(Aspect.COMMAND_BATCHING)) {
412
+ return operation.canRetryWrite && isRetryableWriteError(error);
413
+ }
414
+
415
+ return (
416
+ (hasWriteAspect && willRetryWrite && isRetryableWriteError(error)) ||
417
+ (hasReadAspect && willRetryRead && isRetryableReadError(error))
418
+ );
419
+ }
319
420
  }
@@ -66,6 +66,12 @@ export abstract class AbstractOperation<TResult = any> {
66
66
  /** Specifies the time an operation will run until it throws a timeout error. */
67
67
  timeoutMS?: number;
68
68
 
69
+ /** Used by commitTransaction to share the retry budget across two executeOperation calls. */
70
+ maxAttempts?: number;
71
+
72
+ /** Tracks how many attempts were made in the last executeOperation call. */
73
+ attemptsMade: number;
74
+
69
75
  private _session: ClientSession | undefined;
70
76
 
71
77
  static aspects?: Set<symbol>;
@@ -82,6 +88,8 @@ export abstract class AbstractOperation<TResult = any> {
82
88
 
83
89
  this.options = options;
84
90
  this.bypassPinningCheck = !!options.bypassPinningCheck;
91
+
92
+ this.attemptsMade = 0;
85
93
  }
86
94
 
87
95
  /** Must match the first key of the command object sent to the server.
@@ -0,0 +1,64 @@
1
+ /* eslint-disable no-restricted-imports*/
2
+
3
+ // We squash the restricted import errors here because we are using type-only imports, which
4
+ // do not impact the driver's actual runtime dependencies.
5
+ // We also allow restricted imports in this file, because we expect this file to be the only place actually importing restricted Node APIs.
6
+
7
+ import type * as os from 'os';
8
+
9
+ import { type MongoClientOptions } from './mongo_client';
10
+
11
+ /**
12
+ * @internal
13
+ *
14
+ * This propery can be set on the global object to allow the driver to require otherwise blocked modules.
15
+ * This is used by our test suite to allow tests to access the `os` module without allowing user code to do so.
16
+ */
17
+ export const ALLOWED_DRIVER_REQUIRE_PROPERTY_NAME = 'allowedDriverRequire';
18
+
19
+ /**
20
+ * @public
21
+ * @experimental
22
+ *
23
+ * Represents the set of dependencies that the driver uses from the [Node.js OS module](https://nodejs.org/api/os.html).
24
+ */
25
+ export type OsAdapter = Pick<typeof os, 'release' | 'platform' | 'arch' | 'type'>;
26
+
27
+ /**
28
+ * @public
29
+ * @experimental
30
+ *
31
+ * This type represents the set of dependencies that the driver needs from the Javascript runtime in order to function.
32
+ */
33
+ export interface RuntimeAdapters {
34
+ os?: OsAdapter;
35
+ }
36
+
37
+ /**
38
+ * @internal
39
+ *
40
+ * Represents a complete, parsed set of runtime adapters. After options parsing, all adapters
41
+ * are always present (either using the user's provided adapter, or defaulting to the Node.js module).
42
+ */
43
+ export interface Runtime {
44
+ os: OsAdapter;
45
+ }
46
+
47
+ /**
48
+ * @internal
49
+ *
50
+ * Given a MongoClientOptions, this function resolves the set of runtime options, providing Nodejs implementations if
51
+ * not provided by in `options`, and returns a `Runtime`.
52
+ */
53
+ export function resolveRuntimeAdapters(options: MongoClientOptions): Runtime {
54
+ (globalThis as any)[ALLOWED_DRIVER_REQUIRE_PROPERTY_NAME] = true;
55
+ try {
56
+ const runtime = {
57
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
58
+ os: options.runtimeAdapters?.os ?? require('os')
59
+ };
60
+ return runtime;
61
+ } finally {
62
+ (globalThis as any)[ALLOWED_DRIVER_REQUIRE_PROPERTY_NAME] = false;
63
+ }
64
+ }
@@ -116,7 +116,7 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
116
116
  let srvRecords;
117
117
 
118
118
  try {
119
- srvRecords = await dns.promises.resolveSrv(this.srvAddress);
119
+ srvRecords = await dns.promises.resolve(this.srvAddress, 'SRV');
120
120
  } catch {
121
121
  this.failure();
122
122
  return;
@@ -145,6 +145,8 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
145
145
  hosts: HostAddress[];
146
146
  retryWrites: boolean;
147
147
  retryReads: boolean;
148
+ maxAdaptiveRetries: number;
149
+ enableOverloadRetargeting: boolean;
148
150
  /** How long to block for server selection before throwing an error */
149
151
  serverSelectionTimeoutMS: number;
150
152
  /** The name of the replica set to connect to */
@@ -207,18 +209,13 @@ export type TopologyEvents = {
207
209
  * @internal
208
210
  */
209
211
  export class Topology extends TypedEventEmitter<TopologyEvents> {
210
- /** @internal */
211
212
  s: TopologyPrivate;
212
- /** @internal */
213
213
  waitQueue: List<ServerSelectionRequest>;
214
- /** @internal */
215
214
  hello?: Document;
216
- /** @internal */
217
215
  _type?: string;
218
216
 
219
217
  client!: MongoClient;
220
218
 
221
- /** @internal */
222
219
  private connectionLock?: Promise<Topology>;
223
220
 
224
221
  /** @event */
@@ -595,7 +592,11 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
595
592
  )
596
593
  );
597
594
  }
598
- if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear();
595
+
596
+ if (!options.timeoutContext || options.timeoutContext.clearServerSelectionTimeout) {
597
+ timeout?.clear();
598
+ }
599
+
599
600
  return transaction.server;
600
601
  }
601
602
 
@@ -666,7 +667,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
666
667
  throw error;
667
668
  } finally {
668
669
  abortListener?.[kDispose]();
669
- if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear();
670
+ if (!options.timeoutContext || options.timeoutContext.clearServerSelectionTimeout) {
671
+ timeout?.clear();
672
+ }
670
673
  }
671
674
  }
672
675
  /**