mongodb 6.5.0 → 6.6.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 (207) hide show
  1. package/README.md +9 -9
  2. package/lib/admin.js +9 -9
  3. package/lib/admin.js.map +1 -1
  4. package/lib/bson.js +33 -22
  5. package/lib/bson.js.map +1 -1
  6. package/lib/bulk/common.js +13 -12
  7. package/lib/bulk/common.js.map +1 -1
  8. package/lib/change_stream.js +28 -18
  9. package/lib/change_stream.js.map +1 -1
  10. package/lib/client-side-encryption/auto_encrypter.js +2 -2
  11. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  12. package/lib/client-side-encryption/client_encryption.js +6 -6
  13. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  14. package/lib/client-side-encryption/providers/aws.js +13 -10
  15. package/lib/client-side-encryption/providers/aws.js.map +1 -1
  16. package/lib/client-side-encryption/providers/azure.js +6 -3
  17. package/lib/client-side-encryption/providers/azure.js.map +1 -1
  18. package/lib/cmap/auth/aws_temporary_credentials.js +140 -0
  19. package/lib/cmap/auth/aws_temporary_credentials.js.map +1 -0
  20. package/lib/cmap/auth/gssapi.js +7 -6
  21. package/lib/cmap/auth/gssapi.js.map +1 -1
  22. package/lib/cmap/auth/mongodb_aws.js +8 -101
  23. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  24. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +1 -1
  25. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -1
  26. package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js +2 -1
  27. package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js.map +1 -1
  28. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +1 -1
  29. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -1
  30. package/lib/cmap/auth/scram.js +2 -2
  31. package/lib/cmap/auth/scram.js.map +1 -1
  32. package/lib/cmap/commands.js +24 -111
  33. package/lib/cmap/commands.js.map +1 -1
  34. package/lib/cmap/connect.js +4 -4
  35. package/lib/cmap/connect.js.map +1 -1
  36. package/lib/cmap/connection.js +61 -36
  37. package/lib/cmap/connection.js.map +1 -1
  38. package/lib/cmap/connection_pool.js +22 -13
  39. package/lib/cmap/connection_pool.js.map +1 -1
  40. package/lib/cmap/handshake/client_metadata.js +2 -2
  41. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  42. package/lib/cmap/wire_protocol/compression.js +8 -8
  43. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  44. package/lib/cmap/wire_protocol/on_demand/document.js +218 -0
  45. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -0
  46. package/lib/cmap/wire_protocol/responses.js +184 -0
  47. package/lib/cmap/wire_protocol/responses.js.map +1 -0
  48. package/lib/collection.js +42 -38
  49. package/lib/collection.js.map +1 -1
  50. package/lib/connection_string.js +4 -6
  51. package/lib/connection_string.js.map +1 -1
  52. package/lib/cursor/abstract_cursor.js +76 -43
  53. package/lib/cursor/abstract_cursor.js.map +1 -1
  54. package/lib/cursor/aggregation_cursor.js +16 -33
  55. package/lib/cursor/aggregation_cursor.js.map +1 -1
  56. package/lib/cursor/find_cursor.js +36 -18
  57. package/lib/cursor/find_cursor.js.map +1 -1
  58. package/lib/cursor/run_command_cursor.js +3 -2
  59. package/lib/cursor/run_command_cursor.js.map +1 -1
  60. package/lib/db.js +15 -19
  61. package/lib/db.js.map +1 -1
  62. package/lib/deps.js +31 -26
  63. package/lib/deps.js.map +1 -1
  64. package/lib/encrypter.js +14 -5
  65. package/lib/encrypter.js.map +1 -1
  66. package/lib/error.js +4 -3
  67. package/lib/error.js.map +1 -1
  68. package/lib/gridfs/download.js +19 -14
  69. package/lib/gridfs/download.js.map +1 -1
  70. package/lib/gridfs/index.js.map +1 -1
  71. package/lib/gridfs/upload.js +6 -1
  72. package/lib/gridfs/upload.js.map +1 -1
  73. package/lib/index.js.map +1 -1
  74. package/lib/mongo_client.js +11 -7
  75. package/lib/mongo_client.js.map +1 -1
  76. package/lib/mongo_logger.js +3 -0
  77. package/lib/mongo_logger.js.map +1 -1
  78. package/lib/operations/aggregate.js +2 -1
  79. package/lib/operations/aggregate.js.map +1 -1
  80. package/lib/operations/command.js +1 -1
  81. package/lib/operations/command.js.map +1 -1
  82. package/lib/operations/create_collection.js +1 -1
  83. package/lib/operations/create_collection.js.map +1 -1
  84. package/lib/operations/delete.js +4 -3
  85. package/lib/operations/delete.js.map +1 -1
  86. package/lib/operations/drop.js +1 -1
  87. package/lib/operations/drop.js.map +1 -1
  88. package/lib/operations/execute_operation.js +23 -8
  89. package/lib/operations/execute_operation.js.map +1 -1
  90. package/lib/operations/find.js +4 -4
  91. package/lib/operations/find.js.map +1 -1
  92. package/lib/operations/get_more.js +2 -1
  93. package/lib/operations/get_more.js.map +1 -1
  94. package/lib/operations/indexes.js +29 -121
  95. package/lib/operations/indexes.js.map +1 -1
  96. package/lib/operations/insert.js +3 -3
  97. package/lib/operations/insert.js.map +1 -1
  98. package/lib/operations/kill_cursors.js +3 -1
  99. package/lib/operations/kill_cursors.js.map +1 -1
  100. package/lib/operations/list_collections.js +1 -1
  101. package/lib/operations/list_collections.js.map +1 -1
  102. package/lib/operations/list_databases.js +1 -1
  103. package/lib/operations/list_databases.js.map +1 -1
  104. package/lib/operations/operation.js.map +1 -1
  105. package/lib/operations/run_command.js +4 -2
  106. package/lib/operations/run_command.js.map +1 -1
  107. package/lib/operations/search_indexes/create.js.map +1 -1
  108. package/lib/operations/stats.js +1 -1
  109. package/lib/operations/stats.js.map +1 -1
  110. package/lib/operations/update.js +1 -1
  111. package/lib/operations/update.js.map +1 -1
  112. package/lib/sdam/common.js.map +1 -1
  113. package/lib/sdam/monitor.js +139 -42
  114. package/lib/sdam/monitor.js.map +1 -1
  115. package/lib/sdam/server.js +5 -15
  116. package/lib/sdam/server.js.map +1 -1
  117. package/lib/sdam/server_description.js +1 -0
  118. package/lib/sdam/server_description.js.map +1 -1
  119. package/lib/sdam/server_selection.js +1 -1
  120. package/lib/sdam/server_selection.js.map +1 -1
  121. package/lib/sdam/srv_polling.js +2 -1
  122. package/lib/sdam/srv_polling.js.map +1 -1
  123. package/lib/sdam/topology.js +67 -54
  124. package/lib/sdam/topology.js.map +1 -1
  125. package/lib/sdam/topology_description.js +10 -0
  126. package/lib/sdam/topology_description.js.map +1 -1
  127. package/lib/sessions.js +133 -93
  128. package/lib/sessions.js.map +1 -1
  129. package/lib/timeout.js +77 -0
  130. package/lib/timeout.js.map +1 -0
  131. package/lib/utils.js +61 -28
  132. package/lib/utils.js.map +1 -1
  133. package/mongodb.d.ts +150 -38
  134. package/package.json +16 -13
  135. package/src/admin.ts +9 -9
  136. package/src/bson.ts +14 -0
  137. package/src/bulk/common.ts +3 -2
  138. package/src/change_stream.ts +39 -30
  139. package/src/client-side-encryption/auto_encrypter.ts +2 -2
  140. package/src/client-side-encryption/client_encryption.ts +6 -6
  141. package/src/client-side-encryption/providers/aws.ts +17 -10
  142. package/src/client-side-encryption/providers/azure.ts +5 -3
  143. package/src/cmap/auth/aws_temporary_credentials.ts +169 -0
  144. package/src/cmap/auth/gssapi.ts +9 -11
  145. package/src/cmap/auth/mongodb_aws.ts +19 -126
  146. package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +1 -1
  147. package/src/cmap/auth/mongodb_oidc/callback_lock_cache.ts +2 -1
  148. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +1 -1
  149. package/src/cmap/auth/scram.ts +2 -2
  150. package/src/cmap/commands.ts +28 -132
  151. package/src/cmap/connect.ts +4 -4
  152. package/src/cmap/connection.ts +107 -43
  153. package/src/cmap/connection_pool.ts +32 -29
  154. package/src/cmap/handshake/client_metadata.ts +2 -5
  155. package/src/cmap/wire_protocol/compression.ts +11 -13
  156. package/src/cmap/wire_protocol/on_demand/document.ts +338 -0
  157. package/src/cmap/wire_protocol/responses.ts +237 -0
  158. package/src/collection.ts +87 -58
  159. package/src/connection_string.ts +9 -7
  160. package/src/cursor/abstract_cursor.ts +102 -38
  161. package/src/cursor/aggregation_cursor.ts +32 -34
  162. package/src/cursor/find_cursor.ts +33 -21
  163. package/src/cursor/list_search_indexes_cursor.ts +1 -1
  164. package/src/cursor/run_command_cursor.ts +3 -2
  165. package/src/db.ts +42 -21
  166. package/src/deps.ts +52 -40
  167. package/src/encrypter.ts +14 -5
  168. package/src/error.ts +9 -3
  169. package/src/gridfs/download.ts +19 -31
  170. package/src/gridfs/index.ts +2 -0
  171. package/src/gridfs/upload.ts +11 -8
  172. package/src/index.ts +13 -5
  173. package/src/mongo_client.ts +21 -15
  174. package/src/mongo_logger.ts +3 -0
  175. package/src/mongo_types.ts +1 -1
  176. package/src/operations/aggregate.ts +2 -1
  177. package/src/operations/command.ts +1 -1
  178. package/src/operations/create_collection.ts +7 -2
  179. package/src/operations/delete.ts +4 -3
  180. package/src/operations/drop.ts +1 -1
  181. package/src/operations/execute_operation.ts +29 -10
  182. package/src/operations/find.ts +13 -14
  183. package/src/operations/get_more.ts +9 -1
  184. package/src/operations/indexes.ts +103 -176
  185. package/src/operations/insert.ts +2 -2
  186. package/src/operations/kill_cursors.ts +3 -2
  187. package/src/operations/list_collections.ts +5 -1
  188. package/src/operations/list_databases.ts +1 -1
  189. package/src/operations/operation.ts +3 -0
  190. package/src/operations/run_command.ts +6 -4
  191. package/src/operations/search_indexes/create.ts +4 -1
  192. package/src/operations/stats.ts +1 -1
  193. package/src/operations/update.ts +7 -7
  194. package/src/sdam/common.ts +8 -2
  195. package/src/sdam/monitor.ts +178 -61
  196. package/src/sdam/server.ts +27 -20
  197. package/src/sdam/server_description.ts +8 -3
  198. package/src/sdam/server_selection.ts +2 -3
  199. package/src/sdam/srv_polling.ts +3 -2
  200. package/src/sdam/topology.ts +114 -117
  201. package/src/sdam/topology_description.ts +14 -4
  202. package/src/sessions.ts +168 -148
  203. package/src/timeout.ts +96 -0
  204. package/src/utils.ts +85 -32
  205. package/lib/operations/common_functions.js +0 -38
  206. package/lib/operations/common_functions.js.map +0 -1
  207. package/src/operations/common_functions.ts +0 -79
@@ -8,8 +8,14 @@ import { LEGACY_HELLO_COMMAND } from '../constants';
8
8
  import { MongoError, MongoErrorLabel, MongoNetworkTimeoutError } from '../error';
9
9
  import { MongoLoggableComponent } from '../mongo_logger';
10
10
  import { CancellationToken, TypedEventEmitter } from '../mongo_types';
11
- import type { Callback, EventEmitterWithState } from '../utils';
12
- import { calculateDurationInMs, makeStateMachine, now, ns } from '../utils';
11
+ import {
12
+ calculateDurationInMs,
13
+ type Callback,
14
+ type EventEmitterWithState,
15
+ makeStateMachine,
16
+ now,
17
+ ns
18
+ } from '../utils';
13
19
  import { ServerType, STATE_CLOSED, STATE_CLOSING } from './common';
14
20
  import {
15
21
  ServerHeartbeatFailedEvent,
@@ -25,8 +31,6 @@ const kServer = Symbol('server');
25
31
  const kMonitorId = Symbol('monitorId');
26
32
  /** @internal */
27
33
  const kCancellationToken = Symbol('cancellationToken');
28
- /** @internal */
29
- const kRoundTripTime = Symbol('roundTripTime');
30
34
 
31
35
  const STATE_IDLE = 'idle';
32
36
  const STATE_MONITORING = 'monitoring';
@@ -100,6 +104,8 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
100
104
  rttPinger?: RTTPinger;
101
105
  /** @internal */
102
106
  override component = MongoLoggableComponent.TOPOLOGY;
107
+ /** @internal */
108
+ private rttSampler: RTTSampler;
103
109
 
104
110
  constructor(server: Server, options: MonitorOptions) {
105
111
  super();
@@ -121,6 +127,7 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
121
127
  });
122
128
  this.isRunningInFaasEnv = getFAASEnv() != null;
123
129
  this.mongoLogger = this[kServer].topology.client?.mongoLogger;
130
+ this.rttSampler = new RTTSampler(10);
124
131
 
125
132
  const cancellationToken = this[kCancellationToken];
126
133
  // TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration
@@ -203,6 +210,26 @@ export class Monitor extends TypedEventEmitter<MonitorEvents> {
203
210
  this.emit('close');
204
211
  stateTransition(this, STATE_CLOSED);
205
212
  }
213
+
214
+ get roundTripTime(): number {
215
+ return this.rttSampler.average();
216
+ }
217
+
218
+ get minRoundTripTime(): number {
219
+ return this.rttSampler.min();
220
+ }
221
+
222
+ get latestRtt(): number {
223
+ return this.rttSampler.last ?? 0; // FIXME: Check if this is acceptable
224
+ }
225
+
226
+ addRttSample(rtt: number) {
227
+ this.rttSampler.addSample(rtt);
228
+ }
229
+
230
+ clearRttSamples() {
231
+ this.rttSampler.clear();
232
+ }
206
233
  }
207
234
 
208
235
  function resetMonitorState(monitor: Monitor) {
@@ -216,6 +243,8 @@ function resetMonitorState(monitor: Monitor) {
216
243
 
217
244
  monitor.connection?.destroy();
218
245
  monitor.connection = null;
246
+
247
+ monitor.clearRttSamples();
219
248
  }
220
249
 
221
250
  function useStreamingProtocol(monitor: Monitor, topologyVersion: TopologyVersion | null): boolean {
@@ -249,7 +278,6 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
249
278
  function onHeartbeatFailed(err: Error) {
250
279
  monitor.connection?.destroy();
251
280
  monitor.connection = null;
252
-
253
281
  monitor.emitAndLogHeartbeat(
254
282
  Server.SERVER_HEARTBEAT_FAILED,
255
283
  monitor[kServer].topology.s.id,
@@ -275,11 +303,15 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
275
303
  hello.isWritablePrimary = hello[LEGACY_HELLO_COMMAND];
276
304
  }
277
305
 
306
+ // NOTE: here we use the latestRtt as this measurement corresponds with the value
307
+ // obtained for this successful heartbeat
278
308
  const duration =
279
309
  isAwaitable && monitor.rttPinger
280
- ? monitor.rttPinger.roundTripTime
310
+ ? monitor.rttPinger.latestRtt ?? calculateDurationInMs(start)
281
311
  : calculateDurationInMs(start);
282
312
 
313
+ monitor.addRttSample(duration);
314
+
283
315
  monitor.emitAndLogHeartbeat(
284
316
  Server.SERVER_HEARTBEAT_SUCCEEDED,
285
317
  monitor[kServer].topology.s.id,
@@ -328,13 +360,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
328
360
  : { socketTimeoutMS: connectTimeoutMS };
329
361
 
330
362
  if (isAwaitable && monitor.rttPinger == null) {
331
- monitor.rttPinger = new RTTPinger(
332
- monitor[kCancellationToken],
333
- Object.assign(
334
- { heartbeatFrequencyMS: monitor.options.heartbeatFrequencyMS },
335
- monitor.connectOptions
336
- )
337
- );
363
+ monitor.rttPinger = new RTTPinger(monitor);
338
364
  }
339
365
 
340
366
  // Record new start time before sending handshake
@@ -351,6 +377,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
351
377
  awaited = false;
352
378
  connection
353
379
  .command(ns('admin.$cmd'), cmd, options)
380
+ // eslint-disable-next-line github/no-then
354
381
  .then(onHeartbeatSucceeded, onHeartbeatFailed);
355
382
 
356
383
  return;
@@ -369,12 +396,15 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
369
396
  connection.destroy();
370
397
  throw error;
371
398
  }
399
+ // eslint-disable-next-line github/no-then
372
400
  })().then(
373
401
  connection => {
374
402
  if (isInCloseState(monitor)) {
375
403
  connection.destroy();
376
404
  return;
377
405
  }
406
+ const duration = calculateDurationInMs(start);
407
+ monitor.addRttSample(duration);
378
408
 
379
409
  monitor.connection = connection;
380
410
  monitor.emitAndLogHeartbeat(
@@ -383,7 +413,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
383
413
  connection.hello?.connectionId,
384
414
  new ServerHeartbeatSucceededEvent(
385
415
  monitor.address,
386
- calculateDurationInMs(start),
416
+ duration,
387
417
  connection.hello,
388
418
  useStreamingProtocol(monitor, connection.hello?.topologyVersion)
389
419
  )
@@ -456,23 +486,30 @@ export class RTTPinger {
456
486
  /** @internal */
457
487
  [kCancellationToken]: CancellationToken;
458
488
  /** @internal */
459
- [kRoundTripTime]: number;
460
- /** @internal */
461
489
  [kMonitorId]: NodeJS.Timeout;
490
+ /** @internal */
491
+ monitor: Monitor;
462
492
  closed: boolean;
493
+ /** @internal */
494
+ latestRtt?: number;
463
495
 
464
- constructor(cancellationToken: CancellationToken, options: RTTPingerOptions) {
496
+ constructor(monitor: Monitor) {
465
497
  this.connection = undefined;
466
- this[kCancellationToken] = cancellationToken;
467
- this[kRoundTripTime] = 0;
498
+ this[kCancellationToken] = monitor[kCancellationToken];
468
499
  this.closed = false;
500
+ this.monitor = monitor;
501
+ this.latestRtt = monitor.latestRtt;
469
502
 
470
- const heartbeatFrequencyMS = options.heartbeatFrequencyMS;
471
- this[kMonitorId] = setTimeout(() => measureRoundTripTime(this, options), heartbeatFrequencyMS);
503
+ const heartbeatFrequencyMS = monitor.options.heartbeatFrequencyMS;
504
+ this[kMonitorId] = setTimeout(() => this.measureRoundTripTime(), heartbeatFrequencyMS);
472
505
  }
473
506
 
474
507
  get roundTripTime(): number {
475
- return this[kRoundTripTime];
508
+ return this.monitor.roundTripTime;
509
+ }
510
+
511
+ get minRoundTripTime(): number {
512
+ return this.monitor.minRoundTripTime;
476
513
  }
477
514
 
478
515
  close(): void {
@@ -482,59 +519,60 @@ export class RTTPinger {
482
519
  this.connection?.destroy();
483
520
  this.connection = undefined;
484
521
  }
485
- }
486
-
487
- function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) {
488
- const start = now();
489
- options.cancellationToken = rttPinger[kCancellationToken];
490
- const heartbeatFrequencyMS = options.heartbeatFrequencyMS;
491
-
492
- if (rttPinger.closed) {
493
- return;
494
- }
495
522
 
496
- function measureAndReschedule(conn?: Connection) {
497
- if (rttPinger.closed) {
523
+ private measureAndReschedule(start?: number, conn?: Connection) {
524
+ if (start == null) {
525
+ start = now();
526
+ }
527
+ if (this.closed) {
498
528
  conn?.destroy();
499
529
  return;
500
530
  }
501
531
 
502
- if (rttPinger.connection == null) {
503
- rttPinger.connection = conn;
532
+ if (this.connection == null) {
533
+ this.connection = conn;
504
534
  }
505
535
 
506
- rttPinger[kRoundTripTime] = calculateDurationInMs(start);
507
- rttPinger[kMonitorId] = setTimeout(
508
- () => measureRoundTripTime(rttPinger, options),
509
- heartbeatFrequencyMS
536
+ this.latestRtt = calculateDurationInMs(start);
537
+ this[kMonitorId] = setTimeout(
538
+ () => this.measureRoundTripTime(),
539
+ this.monitor.options.heartbeatFrequencyMS
510
540
  );
511
541
  }
512
542
 
513
- const connection = rttPinger.connection;
514
- if (connection == null) {
515
- connect(options).then(
516
- connection => {
517
- measureAndReschedule(connection);
518
- },
543
+ private measureRoundTripTime() {
544
+ const start = now();
545
+
546
+ if (this.closed) {
547
+ return;
548
+ }
549
+
550
+ const connection = this.connection;
551
+ if (connection == null) {
552
+ // eslint-disable-next-line github/no-then
553
+ connect(this.monitor.connectOptions).then(
554
+ connection => {
555
+ this.measureAndReschedule(start, connection);
556
+ },
557
+ () => {
558
+ this.connection = undefined;
559
+ }
560
+ );
561
+ return;
562
+ }
563
+
564
+ const commandName =
565
+ connection.serverApi?.version || connection.helloOk ? 'hello' : LEGACY_HELLO_COMMAND;
566
+ // eslint-disable-next-line github/no-then
567
+ connection.command(ns('admin.$cmd'), { [commandName]: 1 }, undefined).then(
568
+ () => this.measureAndReschedule(),
519
569
  () => {
520
- rttPinger.connection = undefined;
521
- rttPinger[kRoundTripTime] = 0;
570
+ this.connection?.destroy();
571
+ this.connection = undefined;
572
+ return;
522
573
  }
523
574
  );
524
- return;
525
575
  }
526
-
527
- const commandName =
528
- connection.serverApi?.version || connection.helloOk ? 'hello' : LEGACY_HELLO_COMMAND;
529
- connection.command(ns('admin.$cmd'), { [commandName]: 1 }, undefined).then(
530
- () => measureAndReschedule(),
531
- () => {
532
- rttPinger.connection?.destroy();
533
- rttPinger.connection = undefined;
534
- rttPinger[kRoundTripTime] = 0;
535
- return;
536
- }
537
- );
538
576
  }
539
577
 
540
578
  /**
@@ -662,3 +700,82 @@ export class MonitorInterval {
662
700
  });
663
701
  };
664
702
  }
703
+
704
+ /** @internal
705
+ * This class implements the RTT sampling logic specified for [CSOT](https://github.com/mongodb/specifications/blob/bbb335e60cd7ea1e0f7cd9a9443cb95fc9d3b64d/source/client-side-operations-timeout/client-side-operations-timeout.md#drivers-use-minimum-rtt-to-short-circuit-operations)
706
+ *
707
+ * This is implemented as a [circular buffer](https://en.wikipedia.org/wiki/Circular_buffer) keeping
708
+ * the most recent `windowSize` samples
709
+ * */
710
+ export class RTTSampler {
711
+ /** Index of the next slot to be overwritten */
712
+ private writeIndex: number;
713
+ private length: number;
714
+ private rttSamples: Float64Array;
715
+
716
+ constructor(windowSize = 10) {
717
+ this.rttSamples = new Float64Array(windowSize);
718
+ this.length = 0;
719
+ this.writeIndex = 0;
720
+ }
721
+
722
+ /**
723
+ * Adds an rtt sample to the end of the circular buffer
724
+ * When `windowSize` samples have been collected, `addSample` overwrites the least recently added
725
+ * sample
726
+ */
727
+ addSample(sample: number) {
728
+ this.rttSamples[this.writeIndex++] = sample;
729
+ if (this.length < this.rttSamples.length) {
730
+ this.length++;
731
+ }
732
+
733
+ this.writeIndex %= this.rttSamples.length;
734
+ }
735
+
736
+ /**
737
+ * When \< 2 samples have been collected, returns 0
738
+ * Otherwise computes the minimum value samples contained in the buffer
739
+ */
740
+ min(): number {
741
+ if (this.length < 2) return 0;
742
+ let min = this.rttSamples[0];
743
+ for (let i = 1; i < this.length; i++) {
744
+ if (this.rttSamples[i] < min) min = this.rttSamples[i];
745
+ }
746
+
747
+ return min;
748
+ }
749
+
750
+ /**
751
+ * Returns mean of samples contained in the buffer
752
+ */
753
+ average(): number {
754
+ if (this.length === 0) return 0;
755
+ let sum = 0;
756
+ for (let i = 0; i < this.length; i++) {
757
+ sum += this.rttSamples[i];
758
+ }
759
+
760
+ return sum / this.length;
761
+ }
762
+
763
+ /**
764
+ * Returns most recently inserted element in the buffer
765
+ * Returns null if the buffer is empty
766
+ * */
767
+ get last(): number | null {
768
+ if (this.length === 0) return null;
769
+ return this.rttSamples[this.writeIndex === 0 ? this.length - 1 : this.writeIndex - 1];
770
+ }
771
+
772
+ /**
773
+ * Clear the buffer
774
+ * NOTE: this does not overwrite the data held in the internal array, just the pointers into
775
+ * this array
776
+ */
777
+ clear() {
778
+ this.length = 0;
779
+ this.writeIndex = 0;
780
+ }
781
+ }
@@ -7,6 +7,7 @@ import {
7
7
  type ConnectionPoolOptions
8
8
  } from '../cmap/connection_pool';
9
9
  import { PoolClearedError } from '../cmap/errors';
10
+ import { type MongoDBResponseConstructor } from '../cmap/wire_protocol/responses';
10
11
  import {
11
12
  APM_EVENTS,
12
13
  CLOSED,
@@ -175,7 +176,8 @@ export class Server extends TypedEventEmitter<ServerEvents> {
175
176
  this.emit(
176
177
  Server.DESCRIPTION_RECEIVED,
177
178
  new ServerDescription(this.description.hostAddress, event.reply, {
178
- roundTripTime: calculateRoundTripTime(this.description.roundTripTime, event.duration)
179
+ roundTripTime: this.monitor?.roundTripTime,
180
+ minRoundTripTime: this.monitor?.minRoundTripTime
179
181
  })
180
182
  );
181
183
 
@@ -261,11 +263,25 @@ export class Server extends TypedEventEmitter<ServerEvents> {
261
263
  }
262
264
  }
263
265
 
264
- /**
265
- * Execute a command
266
- * @internal
267
- */
268
- async command(ns: MongoDBNamespace, cmd: Document, options: CommandOptions): Promise<Document> {
266
+ public async command<T extends MongoDBResponseConstructor>(
267
+ ns: MongoDBNamespace,
268
+ command: Document,
269
+ options: CommandOptions | undefined,
270
+ responseType: T | undefined
271
+ ): Promise<typeof responseType extends undefined ? Document : InstanceType<T>>;
272
+
273
+ public async command(
274
+ ns: MongoDBNamespace,
275
+ command: Document,
276
+ options?: CommandOptions
277
+ ): Promise<Document>;
278
+
279
+ public async command(
280
+ ns: MongoDBNamespace,
281
+ cmd: Document,
282
+ options: CommandOptions,
283
+ responseType?: MongoDBResponseConstructor
284
+ ): Promise<Document> {
269
285
  if (ns.db == null || typeof ns === 'string') {
270
286
  throw new MongoInvalidArgumentError('Namespace must not be a string');
271
287
  }
@@ -307,7 +323,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
307
323
 
308
324
  try {
309
325
  try {
310
- return await conn.command(ns, cmd, finalOptions);
326
+ return await conn.command(ns, cmd, finalOptions, responseType);
311
327
  } catch (commandError) {
312
328
  throw this.decorateCommandError(conn, cmd, finalOptions, commandError);
313
329
  }
@@ -318,7 +334,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
318
334
  ) {
319
335
  await this.pool.reauthenticate(conn);
320
336
  try {
321
- return await conn.command(ns, cmd, finalOptions);
337
+ return await conn.command(ns, cmd, finalOptions, responseType);
322
338
  } catch (commandError) {
323
339
  throw this.decorateCommandError(conn, cmd, finalOptions, commandError);
324
340
  }
@@ -357,7 +373,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
357
373
  // clear for the specific service id.
358
374
  if (!this.loadBalanced) {
359
375
  error.addErrorLabel(MongoErrorLabel.ResetPool);
360
- markServerUnknown(this, error as MongoServerError);
376
+ markServerUnknown(this, error);
361
377
  } else if (connection) {
362
378
  this.pool.clear({ serviceId: connection.serviceId });
363
379
  }
@@ -373,7 +389,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
373
389
  if (shouldClearPool) {
374
390
  error.addErrorLabel(MongoErrorLabel.ResetPool);
375
391
  }
376
- markServerUnknown(this, error as MongoServerError);
392
+ markServerUnknown(this, error);
377
393
  process.nextTick(() => this.requestCheck());
378
394
  }
379
395
  }
@@ -467,16 +483,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
467
483
  }
468
484
  }
469
485
 
470
- function calculateRoundTripTime(oldRtt: number, duration: number): number {
471
- if (oldRtt === -1) {
472
- return duration;
473
- }
474
-
475
- const alpha = 0.2;
476
- return alpha * duration + (1 - alpha) * oldRtt;
477
- }
478
-
479
- function markServerUnknown(server: Server, error?: MongoServerError) {
486
+ function markServerUnknown(server: Server, error?: MongoError) {
480
487
  // Load balancer servers can never be marked unknown.
481
488
  if (server.loadBalanced) {
482
489
  return;
@@ -1,5 +1,5 @@
1
1
  import { type Document, Long, type ObjectId } from '../bson';
2
- import { type MongoError, MongoRuntimeError, type MongoServerError } from '../error';
2
+ import { type MongoError, MongoRuntimeError } from '../error';
3
3
  import { arrayStrictEqual, compareObjectId, errorStrictEqual, HostAddress, now } from '../utils';
4
4
  import type { ClusterTime } from './common';
5
5
  import { ServerType } from './common';
@@ -31,10 +31,12 @@ export type TagSet = { [key: string]: string };
31
31
  /** @internal */
32
32
  export interface ServerDescriptionOptions {
33
33
  /** An Error used for better reporting debugging */
34
- error?: MongoServerError;
34
+ error?: MongoError;
35
35
 
36
- /** The round trip time to ping this server (in ms) */
36
+ /** The average round trip time to ping this server (in ms) */
37
37
  roundTripTime?: number;
38
+ /** The minimum round trip time to ping this server over the past 10 samples(in ms) */
39
+ minRoundTripTime?: number;
38
40
 
39
41
  /** If the client is in load balancing mode. */
40
42
  loadBalanced?: boolean;
@@ -58,6 +60,8 @@ export class ServerDescription {
58
60
  minWireVersion: number;
59
61
  maxWireVersion: number;
60
62
  roundTripTime: number;
63
+ /** The minimum measurement of the last 10 measurements of roundTripTime that have been collected */
64
+ minRoundTripTime: number;
61
65
  lastUpdateTime: number;
62
66
  lastWriteDate: number;
63
67
  me: string | null;
@@ -98,6 +102,7 @@ export class ServerDescription {
98
102
  this.minWireVersion = hello?.minWireVersion ?? 0;
99
103
  this.maxWireVersion = hello?.maxWireVersion ?? 0;
100
104
  this.roundTripTime = options?.roundTripTime ?? -1;
105
+ this.minRoundTripTime = options?.minRoundTripTime ?? 0;
101
106
  this.lastUpdateTime = now();
102
107
  this.lastWriteDate = hello?.lastWrite?.lastWriteDate ?? 0;
103
108
  this.error = options.error ?? null;
@@ -223,9 +223,8 @@ function latencyWindowReducer(
223
223
  servers: ServerDescription[]
224
224
  ): ServerDescription[] {
225
225
  const low = servers.reduce(
226
- (min: number, server: ServerDescription) =>
227
- min === -1 ? server.roundTripTime : Math.min(server.roundTripTime, min),
228
- -1
226
+ (min: number, server: ServerDescription) => Math.min(server.roundTripTime, min),
227
+ Infinity
229
228
  );
230
229
 
231
230
  const high = low + topologyDescription.localThresholdMS;
@@ -3,7 +3,7 @@ import { clearTimeout, setTimeout } from 'timers';
3
3
 
4
4
  import { MongoRuntimeError } from '../error';
5
5
  import { TypedEventEmitter } from '../mongo_types';
6
- import { HostAddress, matchesParentDomain } from '../utils';
6
+ import { HostAddress, matchesParentDomain, squashError } from '../utils';
7
7
 
8
8
  /**
9
9
  * @internal
@@ -95,7 +95,8 @@ export class SrvPoller extends TypedEventEmitter<SrvPollerEvents> {
95
95
  }
96
96
 
97
97
  this._timeout = setTimeout(() => {
98
- this._poll().catch(() => null);
98
+ // eslint-disable-next-line github/no-then
99
+ this._poll().then(undefined, squashError);
99
100
  }, this.intervalMS);
100
101
  }
101
102