mongodb 3.5.8 → 3.5.9

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.
package/HISTORY.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ <a name="3.5.9"></a>
6
+ ## [3.5.9](https://github.com/mongodb/node-mongodb-native/compare/v3.5.8...v3.5.9) (2020-06-12)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * don't try to calculate sMax if there are no viable servers ([be51347](https://github.com/mongodb/node-mongodb-native/commit/be51347))
12
+ * use async interruptable interval for server monitoring ([1f855a4](https://github.com/mongodb/node-mongodb-native/commit/1f855a4))
13
+ * use duration of handshake if no previous roundTripTime exists ([ddfa41b](https://github.com/mongodb/node-mongodb-native/commit/ddfa41b))
14
+
15
+
16
+ ### Features
17
+
18
+ * introduce an interruptable async interval timer ([9e12cd5](https://github.com/mongodb/node-mongodb-native/commit/9e12cd5))
19
+
20
+
21
+
5
22
  <a name="3.5.8"></a>
6
23
  ## [3.5.8](https://github.com/mongodb/node-mongodb-native/compare/v3.5.7...v3.5.8) (2020-05-28)
7
24
 
@@ -8,6 +8,8 @@ const Cursor = require('./cursor');
8
8
  const relayEvents = require('./core/utils').relayEvents;
9
9
  const maxWireVersion = require('./core/utils').maxWireVersion;
10
10
  const maybePromise = require('./utils').maybePromise;
11
+ const now = require('./utils').now;
12
+ const calculateDurationInMs = require('./utils').calculateDurationInMs;
11
13
  const AggregateOperation = require('./operations/aggregate');
12
14
 
13
15
  const kResumeQueue = Symbol('resumeQueue');
@@ -459,15 +461,21 @@ function applyKnownOptions(target, source, optionNames) {
459
461
  const SELECTION_TIMEOUT = 30000;
460
462
  function waitForTopologyConnected(topology, options, callback) {
461
463
  setTimeout(() => {
462
- if (options && options.start == null) options.start = process.hrtime();
463
- const start = options.start || process.hrtime();
464
+ if (options && options.start == null) {
465
+ options.start = now();
466
+ }
467
+
468
+ const start = options.start || now();
464
469
  const timeout = options.timeout || SELECTION_TIMEOUT;
465
470
  const readPreference = options.readPreference;
471
+ if (topology.isConnected({ readPreference })) {
472
+ return callback();
473
+ }
474
+
475
+ if (calculateDurationInMs(start) > timeout) {
476
+ return callback(new MongoError('Timed out waiting for connection'));
477
+ }
466
478
 
467
- if (topology.isConnected({ readPreference })) return callback();
468
- const hrElapsed = process.hrtime(start);
469
- const elapsed = (hrElapsed[0] * 1e9 + hrElapsed[1]) / 1e6;
470
- if (elapsed > timeout) return callback(new MongoError('Timed out waiting for connection'));
471
479
  waitForTopologyConnected(topology, options, callback);
472
480
  }, 500); // this is an arbitrary wait time to allow SDAM to transition
473
481
  }
@@ -11,6 +11,8 @@ const wp = require('../core/wireprotocol');
11
11
  const apm = require('../core/connection/apm');
12
12
  const updateSessionFromResponse = require('../core/sessions').updateSessionFromResponse;
13
13
  const uuidV4 = require('../core/utils').uuidV4;
14
+ const now = require('../utils').now;
15
+ const calculateDurationInMs = require('../utils').calculateDurationInMs;
14
16
 
15
17
  const kStream = Symbol('stream');
16
18
  const kQueue = Symbol('queue');
@@ -37,7 +39,7 @@ class Connection extends EventEmitter {
37
39
 
38
40
  this[kDescription] = new StreamDescription(this.address, options);
39
41
  this[kGeneration] = options.generation;
40
- this[kLastUseTime] = Date.now();
42
+ this[kLastUseTime] = now();
41
43
 
42
44
  // retain a reference to an `AutoEncrypter` if present
43
45
  if (options.autoEncrypter) {
@@ -108,7 +110,7 @@ class Connection extends EventEmitter {
108
110
  }
109
111
 
110
112
  get idleTime() {
111
- return Date.now() - this[kLastUseTime];
113
+ return calculateDurationInMs(this[kLastUseTime]);
112
114
  }
113
115
 
114
116
  get clusterTime() {
@@ -120,7 +122,7 @@ class Connection extends EventEmitter {
120
122
  }
121
123
 
122
124
  markAvailable() {
123
- this[kLastUseTime] = Date.now();
125
+ this[kLastUseTime] = now();
124
126
  }
125
127
 
126
128
  destroy(options, callback) {
@@ -326,7 +328,7 @@ function write(command, options, callback) {
326
328
  if (this.monitorCommands) {
327
329
  this.emit('commandStarted', new apm.CommandStartedEvent(this, command));
328
330
 
329
- operationDescription.started = process.hrtime();
331
+ operationDescription.started = now();
330
332
  operationDescription.cb = (err, reply) => {
331
333
  if (err) {
332
334
  this.emit(
@@ -2,7 +2,7 @@
2
2
  const Msg = require('../connection/msg').Msg;
3
3
  const KillCursor = require('../connection/commands').KillCursor;
4
4
  const GetMore = require('../connection/commands').GetMore;
5
- const calculateDurationInMs = require('../utils').calculateDurationInMs;
5
+ const calculateDurationInMs = require('../../utils').calculateDurationInMs;
6
6
 
7
7
  /** Commands that we want to redact because of the sensitive nature of their contents */
8
8
  const SENSITIVE_COMMANDS = new Set([
@@ -21,6 +21,7 @@ const connect = require('./connect');
21
21
  const updateSessionFromResponse = require('../sessions').updateSessionFromResponse;
22
22
  const eachAsync = require('../utils').eachAsync;
23
23
  const makeStateMachine = require('../utils').makeStateMachine;
24
+ const now = require('../../utils').now;
24
25
 
25
26
  const DISCONNECTED = 'disconnected';
26
27
  const CONNECTING = 'connecting';
@@ -898,7 +899,7 @@ Pool.prototype.write = function(command, options, cb) {
898
899
  if (self.options.monitorCommands) {
899
900
  this.emit('commandStarted', new apm.CommandStartedEvent(this, command));
900
901
 
901
- operation.started = process.hrtime();
902
+ operation.started = now();
902
903
  operation.cb = (err, reply) => {
903
904
  if (err) {
904
905
  self.emit(
@@ -1,13 +1,15 @@
1
1
  'use strict';
2
2
 
3
3
  const ServerType = require('./common').ServerType;
4
- const calculateDurationInMs = require('../utils').calculateDurationInMs;
5
4
  const EventEmitter = require('events');
6
5
  const connect = require('../connection/connect');
7
6
  const Connection = require('../../cmap/connection').Connection;
8
7
  const common = require('./common');
9
8
  const makeStateMachine = require('../utils').makeStateMachine;
10
9
  const MongoError = require('../error').MongoError;
10
+ const makeInterruptableAsyncInterval = require('../../utils').makeInterruptableAsyncInterval;
11
+ const calculateDurationInMs = require('../../utils').calculateDurationInMs;
12
+ const now = require('../../utils').now;
11
13
 
12
14
  const sdamEvents = require('./events');
13
15
  const ServerHeartbeatStartedEvent = sdamEvents.ServerHeartbeatStartedEvent;
@@ -18,7 +20,6 @@ const kServer = Symbol('server');
18
20
  const kMonitorId = Symbol('monitorId');
19
21
  const kConnection = Symbol('connection');
20
22
  const kCancellationToken = Symbol('cancellationToken');
21
- const kLastCheckTime = Symbol('lastCheckTime');
22
23
 
23
24
  const STATE_CLOSED = common.STATE_CLOSED;
24
25
  const STATE_CLOSING = common.STATE_CLOSING;
@@ -33,6 +34,10 @@ const stateTransition = makeStateMachine({
33
34
 
34
35
  const INVALID_REQUEST_CHECK_STATES = new Set([STATE_CLOSING, STATE_CLOSED, STATE_MONITORING]);
35
36
 
37
+ function isInCloseState(monitor) {
38
+ return monitor.s.state === STATE_CLOSED || monitor.s.state === STATE_CLOSING;
39
+ }
40
+
36
41
  class Monitor extends EventEmitter {
37
42
  constructor(server, options) {
38
43
  super(options);
@@ -41,6 +46,7 @@ class Monitor extends EventEmitter {
41
46
  this[kConnection] = undefined;
42
47
  this[kCancellationToken] = new EventEmitter();
43
48
  this[kCancellationToken].setMaxListeners(Infinity);
49
+ this[kMonitorId] = null;
44
50
  this.s = {
45
51
  state: STATE_CLOSED
46
52
  };
@@ -89,7 +95,14 @@ class Monitor extends EventEmitter {
89
95
  return;
90
96
  }
91
97
 
92
- monitorServer(this);
98
+ // start
99
+ const heartbeatFrequencyMS = this.options.heartbeatFrequencyMS;
100
+ const minHeartbeatFrequencyMS = this.options.minHeartbeatFrequencyMS;
101
+ this[kMonitorId] = makeInterruptableAsyncInterval(monitorServer(this), {
102
+ interval: heartbeatFrequencyMS,
103
+ minInterval: minHeartbeatFrequencyMS,
104
+ immediate: true
105
+ });
93
106
  }
94
107
 
95
108
  requestCheck() {
@@ -97,31 +110,19 @@ class Monitor extends EventEmitter {
97
110
  return;
98
111
  }
99
112
 
100
- const heartbeatFrequencyMS = this.options.heartbeatFrequencyMS;
101
- const minHeartbeatFrequencyMS = this.options.minHeartbeatFrequencyMS;
102
- const remainingTime = heartbeatFrequencyMS - calculateDurationInMs(this[kLastCheckTime]);
103
- if (remainingTime > minHeartbeatFrequencyMS && this[kMonitorId]) {
104
- clearTimeout(this[kMonitorId]);
105
- rescheduleMonitoring(this, minHeartbeatFrequencyMS);
106
- return;
107
- }
108
-
109
- if (this[kMonitorId]) {
110
- clearTimeout(this[kMonitorId]);
111
- }
112
-
113
- monitorServer(this);
113
+ this[kMonitorId].wake();
114
114
  }
115
115
 
116
116
  close() {
117
- if (this.s.state === STATE_CLOSED || this.s.state === STATE_CLOSING) {
117
+ if (isInCloseState(this)) {
118
118
  return;
119
119
  }
120
120
 
121
121
  stateTransition(this, STATE_CLOSING);
122
122
  this[kCancellationToken].emit('cancel');
123
123
  if (this[kMonitorId]) {
124
- clearTimeout(this[kMonitorId]);
124
+ this[kMonitorId].stop();
125
+ this[kMonitorId] = null;
125
126
  }
126
127
 
127
128
  if (this[kConnection]) {
@@ -138,7 +139,7 @@ function checkServer(monitor, callback) {
138
139
  monitor[kConnection] = undefined;
139
140
  }
140
141
 
141
- const start = process.hrtime();
142
+ const start = now();
142
143
  monitor.emit('serverHeartbeatStarted', new ServerHeartbeatStartedEvent(monitor.address));
143
144
 
144
145
  function failureHandler(err) {
@@ -186,7 +187,7 @@ function checkServer(monitor, callback) {
186
187
  return;
187
188
  }
188
189
 
189
- if (monitor.s.state === STATE_CLOSING || monitor.s.state === STATE_CLOSED) {
190
+ if (isInCloseState(monitor)) {
190
191
  conn.destroy({ force: true });
191
192
  failureHandler(new MongoError('monitor was destroyed'));
192
193
  return;
@@ -198,52 +199,44 @@ function checkServer(monitor, callback) {
198
199
  }
199
200
 
200
201
  function monitorServer(monitor) {
201
- stateTransition(monitor, STATE_MONITORING);
202
-
203
- // TODO: the next line is a legacy event, remove in v4
204
- process.nextTick(() => monitor.emit('monitoring', monitor[kServer]));
202
+ return callback => {
203
+ stateTransition(monitor, STATE_MONITORING);
204
+ function done() {
205
+ if (!isInCloseState(monitor)) {
206
+ stateTransition(monitor, STATE_IDLE);
207
+ }
205
208
 
206
- checkServer(monitor, e0 => {
207
- if (e0 == null) {
208
- rescheduleMonitoring(monitor);
209
- return;
209
+ callback();
210
210
  }
211
211
 
212
- // otherwise an error occured on initial discovery, also bail
213
- if (monitor[kServer].description.type === ServerType.Unknown) {
214
- monitor.emit('resetServer', e0);
215
- rescheduleMonitoring(monitor);
216
- return;
217
- }
212
+ // TODO: the next line is a legacy event, remove in v4
213
+ process.nextTick(() => monitor.emit('monitoring', monitor[kServer]));
218
214
 
219
- // According to the SDAM specification's "Network error during server check" section, if
220
- // an ismaster call fails we reset the server's pool. If a server was once connected,
221
- // change its type to `Unknown` only after retrying once.
222
- monitor.emit('resetConnectionPool');
223
-
224
- checkServer(monitor, e1 => {
225
- if (e1) {
226
- monitor.emit('resetServer', e1);
215
+ checkServer(monitor, e0 => {
216
+ if (e0 == null) {
217
+ return done();
227
218
  }
228
219
 
229
- rescheduleMonitoring(monitor);
230
- });
231
- });
232
- }
220
+ // otherwise an error occured on initial discovery, also bail
221
+ if (monitor[kServer].description.type === ServerType.Unknown) {
222
+ monitor.emit('resetServer', e0);
223
+ return done();
224
+ }
233
225
 
234
- function rescheduleMonitoring(monitor, ms) {
235
- const heartbeatFrequencyMS = monitor.options.heartbeatFrequencyMS;
236
- if (monitor.s.state === STATE_CLOSING || monitor.s.state === STATE_CLOSED) {
237
- return;
238
- }
226
+ // According to the SDAM specification's "Network error during server check" section, if
227
+ // an ismaster call fails we reset the server's pool. If a server was once connected,
228
+ // change its type to `Unknown` only after retrying once.
229
+ monitor.emit('resetConnectionPool');
239
230
 
240
- stateTransition(monitor, STATE_IDLE);
231
+ checkServer(monitor, e1 => {
232
+ if (e1) {
233
+ monitor.emit('resetServer', e1);
234
+ }
241
235
 
242
- monitor[kLastCheckTime] = process.hrtime();
243
- monitor[kMonitorId] = setTimeout(() => {
244
- monitor[kMonitorId] = undefined;
245
- monitor.requestCheck();
246
- }, ms || heartbeatFrequencyMS);
236
+ done();
237
+ });
238
+ });
239
+ };
247
240
  }
248
241
 
249
242
  module.exports = {
@@ -415,6 +415,10 @@ Object.defineProperty(Server.prototype, 'clusterTime', {
415
415
  });
416
416
 
417
417
  function calculateRoundTripTime(oldRtt, duration) {
418
+ if (oldRtt === -1) {
419
+ return duration;
420
+ }
421
+
418
422
  const alpha = 0.2;
419
423
  return alpha * duration + (1 - alpha) * oldRtt;
420
424
  }
@@ -4,6 +4,7 @@ const arrayStrictEqual = require('../utils').arrayStrictEqual;
4
4
  const tagsStrictEqual = require('../utils').tagsStrictEqual;
5
5
  const errorStrictEqual = require('../utils').errorStrictEqual;
6
6
  const ServerType = require('./common').ServerType;
7
+ const now = require('../../utils').now;
7
8
 
8
9
  const WRITABLE_SERVER_TYPES = new Set([
9
10
  ServerType.RSPrimary,
@@ -70,7 +71,7 @@ class ServerDescription {
70
71
  this.address = address;
71
72
  this.error = options.error;
72
73
  this.roundTripTime = options.roundTripTime || -1;
73
- this.lastUpdateTime = Date.now();
74
+ this.lastUpdateTime = now();
74
75
  this.lastWriteDate = ismaster.lastWrite ? ismaster.lastWrite.lastWriteDate : null;
75
76
  this.opTime = ismaster.lastWrite ? ismaster.lastWrite.opTime : null;
76
77
  this.type = parseServerType(ismaster);
@@ -60,7 +60,13 @@ function maxStalenessReducer(readPreference, topologyDescription, servers) {
60
60
  if (staleness <= readPreference.maxStalenessSeconds) result.push(server);
61
61
  return result;
62
62
  }, []);
63
- } else if (topologyDescription.type === TopologyType.ReplicaSetNoPrimary) {
63
+ }
64
+
65
+ if (topologyDescription.type === TopologyType.ReplicaSetNoPrimary) {
66
+ if (servers.length === 0) {
67
+ return servers;
68
+ }
69
+
64
70
  const sMax = servers.reduce((max, s) => (s.lastWriteDate > max.lastWriteDate ? s : max));
65
71
  return servers.reduce((result, server) => {
66
72
  const stalenessMS =
@@ -17,7 +17,8 @@ const isTransactionCommand = require('./transactions').isTransactionCommand;
17
17
  const resolveClusterTime = require('./topologies/shared').resolveClusterTime;
18
18
  const isSharded = require('./wireprotocol/shared').isSharded;
19
19
  const maxWireVersion = require('./utils').maxWireVersion;
20
-
20
+ const now = require('./../utils').now;
21
+ const calculateDurationInMs = require('./../utils').calculateDurationInMs;
21
22
  const minWireVersionForShardedTransactions = 8;
22
23
 
23
24
  function assertAlive(session, callback) {
@@ -285,7 +286,7 @@ class ClientSession extends EventEmitter {
285
286
  * @param {TransactionOptions} [options] Optional settings for the transaction
286
287
  */
287
288
  withTransaction(fn, options) {
288
- const startTime = Date.now();
289
+ const startTime = now();
289
290
  return attemptTransaction(this, startTime, fn, options);
290
291
  }
291
292
  }
@@ -301,7 +302,7 @@ const NON_DETERMINISTIC_WRITE_CONCERN_ERRORS = new Set([
301
302
  ]);
302
303
 
303
304
  function hasNotTimedOut(startTime, max) {
304
- return Date.now() - startTime < max;
305
+ return calculateDurationInMs(startTime) < max;
305
306
  }
306
307
 
307
308
  function isUnknownTransactionCommitResult(err) {
@@ -558,7 +559,7 @@ function supportsRecoveryToken(session) {
558
559
  class ServerSession {
559
560
  constructor() {
560
561
  this.id = { id: new Binary(uuidV4(), Binary.SUBTYPE_UUID) };
561
- this.lastUse = Date.now();
562
+ this.lastUse = now();
562
563
  this.txnNumber = 0;
563
564
  this.isDirty = false;
564
565
  }
@@ -573,7 +574,7 @@ class ServerSession {
573
574
  // Take the difference of the lastUse timestamp and now, which will result in a value in
574
575
  // milliseconds, and then convert milliseconds to minutes to compare to `sessionTimeoutMinutes`
575
576
  const idleTimeMinutes = Math.round(
576
- (((Date.now() - this.lastUse) % 86400000) % 3600000) / 60000
577
+ ((calculateDurationInMs(this.lastUse) % 86400000) % 3600000) / 60000
577
578
  );
578
579
 
579
580
  return idleTimeMinutes > sessionTimeoutMinutes - 1;
@@ -708,7 +709,7 @@ function applySession(session, command, options) {
708
709
  }
709
710
 
710
711
  const serverSession = session.serverSession;
711
- serverSession.lastUse = Date.now();
712
+ serverSession.lastUse = now();
712
713
  command.lsid = serverSession.id;
713
714
 
714
715
  // first apply non-transaction-specific sessions data
@@ -17,9 +17,10 @@ const isRetryableWritesSupported = require('./shared').isRetryableWritesSupporte
17
17
  const relayEvents = require('../utils').relayEvents;
18
18
  const isRetryableError = require('../error').isRetryableError;
19
19
  const BSON = retrieveBSON();
20
- const calculateDurationInMs = require('../utils').calculateDurationInMs;
21
20
  const getMMAPError = require('./shared').getMMAPError;
22
21
  const makeClientMetadata = require('../utils').makeClientMetadata;
22
+ const now = require('../../utils').now;
23
+ const calculateDurationInMs = require('../../utils').calculateDurationInMs;
23
24
 
24
25
  //
25
26
  // States
@@ -426,8 +427,7 @@ var pingServer = function(self, server, cb) {
426
427
  var latencyMS = new Date().getTime() - start;
427
428
 
428
429
  // Set the last updatedTime
429
- var hrtime = process.hrtime();
430
- server.lastUpdateTime = (hrtime[0] * 1e9 + hrtime[1]) / 1e6;
430
+ server.lastUpdateTime = now();
431
431
 
432
432
  // We had an error, remove it from the state
433
433
  if (err) {
@@ -1127,7 +1127,7 @@ ReplSet.prototype.selectServer = function(selector, options, callback) {
1127
1127
  }
1128
1128
 
1129
1129
  let lastError;
1130
- const start = process.hrtime();
1130
+ const start = now();
1131
1131
  const _selectServer = () => {
1132
1132
  if (calculateDurationInMs(start) >= SERVER_SELECTION_TIMEOUT_MS) {
1133
1133
  if (lastError != null) {
package/lib/core/utils.js CHANGED
@@ -13,17 +13,6 @@ const uuidV4 = () => {
13
13
  return result;
14
14
  };
15
15
 
16
- /**
17
- * Returns the duration calculated from two high resolution timers in milliseconds
18
- *
19
- * @param {Object} started A high resolution timestamp created from `process.hrtime()`
20
- * @returns {Number} The duration in milliseconds
21
- */
22
- const calculateDurationInMs = started => {
23
- const hrtime = process.hrtime(started);
24
- return (hrtime[0] * 1e9 + hrtime[1]) / 1e6;
25
- };
26
-
27
16
  /**
28
17
  * Relays events for a given listener and emitter
29
18
  *
@@ -261,7 +250,6 @@ const noop = () => {};
261
250
 
262
251
  module.exports = {
263
252
  uuidV4,
264
- calculateDurationInMs,
265
253
  relayEvents,
266
254
  collationNotSupported,
267
255
  retrieveEJSON,
@@ -62,8 +62,13 @@ function getMore(server, ns, cursorState, batchSize, options, callback) {
62
62
  return;
63
63
  }
64
64
 
65
+ const cursorId =
66
+ cursorState.cursorId instanceof Long
67
+ ? cursorState.cursorId
68
+ : Long.fromNumber(cursorState.cursorId);
69
+
65
70
  const getMoreCmd = {
66
- getMore: cursorState.cursorId,
71
+ getMore: cursorId,
67
72
  collection: collectionNamespace(ns),
68
73
  batchSize: Math.abs(batchSize)
69
74
  };
@@ -146,6 +146,10 @@ const validOptions = require('./operations/connect').validOptions;
146
146
  * @param {Number} [options.localThresholdMS=15] **Only applies to the unified topology** The size of the latency window for selecting among multiple suitable servers
147
147
  * @param {Number} [options.serverSelectionTimeoutMS=30000] **Only applies to the unified topology** How long to block for server selection before throwing an error
148
148
  * @param {Number} [options.heartbeatFrequencyMS=10000] **Only applies to the unified topology** The frequency with which topology updates are scheduled
149
+ * @param {number} [options.maxPoolSize=10] **Only applies to the unified topology** The maximum number of connections that may be associated with a pool at a given time. This includes in use and available connections.
150
+ * @param {number} [options.minPoolSize=0] **Only applies to the unified topology** The minimum number of connections that MUST exist at any moment in a single connection pool.
151
+ * @param {number} [options.maxIdleTimeMS] **Only applies to the unified topology** The maximum amount of time a connection should remain idle in the connection pool before being marked idle. The default is infinity.
152
+ * @param {number} [options.waitQueueTimeoutMS=0] **Only applies to the unified topology** The maximum amount of time operation execution should wait for a connection to become available. The default is 0 which means there is no limit.
149
153
  * @param {AutoEncrypter~AutoEncryptionOptions} [options.autoEncryption] Optionally enable client side auto encryption
150
154
  * @param {DriverInfoOptions} [options.driverInfo] Allows a wrapping driver to amend the client metadata generated by the driver to include information about the wrapping driver
151
155
  * @param {MongoClient~connectCallback} [callback] The command result callback
@@ -415,6 +419,10 @@ MongoClient.prototype.isConnected = function(options) {
415
419
  * @param {Number} [options.localThresholdMS=15] **Only applies to the unified topology** The size of the latency window for selecting among multiple suitable servers
416
420
  * @param {Number} [options.serverSelectionTimeoutMS=30000] **Only applies to the unified topology** How long to block for server selection before throwing an error
417
421
  * @param {Number} [options.heartbeatFrequencyMS=10000] **Only applies to the unified topology** The frequency with which topology updates are scheduled
422
+ * @param {number} [options.maxPoolSize=10] **Only applies to the unified topology** The maximum number of connections that may be associated with a pool at a given time. This includes in use and available connections.
423
+ * @param {number} [options.minPoolSize=0] **Only applies to the unified topology** The minimum number of connections that MUST exist at any moment in a single connection pool.
424
+ * @param {number} [options.maxIdleTimeMS] **Only applies to the unified topology** The maximum amount of time a connection should remain idle in the connection pool before being marked idle. The default is infinity.
425
+ * @param {number} [options.waitQueueTimeoutMS=0] **Only applies to the unified topology** The maximum amount of time operation execution should wait for a connection to become available. The default is 0 which means there is no limit.
418
426
  * @param {AutoEncrypter~AutoEncryptionOptions} [options.autoEncryption] Optionally enable client side auto encryption
419
427
  * @param {DriverInfoOptions} [options.driverInfo] Allows a wrapping driver to amend the client metadata generated by the driver to include information about the wrapping driver
420
428
  * @param {MongoClient~connectCallback} [callback] The command result callback
@@ -151,6 +151,11 @@ const validOptionNames = [
151
151
  'tlsCertificateKeyFilePassword',
152
152
  'minHeartbeatFrequencyMS',
153
153
  'heartbeatFrequencyMS',
154
+
155
+ // CMAP options
156
+ 'maxPoolSize',
157
+ 'minPoolSize',
158
+ 'maxIdleTimeMS',
154
159
  'waitQueueTimeoutMS'
155
160
  ];
156
161
 
@@ -15,8 +15,18 @@ class NativeTopology extends Topology {
15
15
  cursorFactory: Cursor,
16
16
  reconnect: false,
17
17
  emitError: typeof options.emitError === 'boolean' ? options.emitError : true,
18
- maxPoolSize: typeof options.poolSize === 'number' ? options.poolSize : 5,
19
- minPoolSize: typeof options.minSize === 'number' ? options.minSize : 0,
18
+ maxPoolSize:
19
+ typeof options.maxPoolSize === 'number'
20
+ ? options.maxPoolSize
21
+ : typeof options.poolSize === 'number'
22
+ ? options.poolSize
23
+ : 10,
24
+ minPoolSize:
25
+ typeof options.minPoolSize === 'number'
26
+ ? options.minPoolSize
27
+ : typeof options.minSize === 'number'
28
+ ? options.minSize
29
+ : 0,
20
30
  monitorCommands:
21
31
  typeof options.monitorCommands === 'boolean' ? options.monitorCommands : false
22
32
  }
package/lib/utils.js CHANGED
@@ -734,6 +734,98 @@ function maybePromise(parent, callback, fn) {
734
734
  return result;
735
735
  }
736
736
 
737
+ function now() {
738
+ const hrtime = process.hrtime();
739
+ return Math.floor(hrtime[0] * 1000 + hrtime[1] / 1000000);
740
+ }
741
+
742
+ function calculateDurationInMs(started) {
743
+ if (typeof started !== 'number') {
744
+ throw TypeError('numeric value required to calculate duration');
745
+ }
746
+
747
+ const elapsed = now() - started;
748
+ return elapsed < 0 ? 0 : elapsed;
749
+ }
750
+
751
+ /**
752
+ * Creates an interval timer which is able to be woken up sooner than
753
+ * the interval. The timer will also debounce multiple calls to wake
754
+ * ensuring that the function is only ever called once within a minimum
755
+ * interval window.
756
+ *
757
+ * @param {function} fn An async function to run on an interval, must accept a `callback` as its only parameter
758
+ * @param {object} [options] Optional settings
759
+ * @param {number} [options.interval] The interval at which to run the provided function
760
+ * @param {number} [options.minInterval] The minimum time which must pass between invocations of the provided function
761
+ * @param {boolean} [options.immediate] Execute the function immediately when the interval is started
762
+ */
763
+ function makeInterruptableAsyncInterval(fn, options) {
764
+ let timerId;
765
+ let lastCallTime;
766
+ let lastWakeTime;
767
+ let stopped = false;
768
+
769
+ options = options || {};
770
+ const interval = options.interval || 1000;
771
+ const minInterval = options.minInterval || 500;
772
+ const immediate = typeof options.immediate === 'boolean' ? options.immediate : false;
773
+
774
+ function wake() {
775
+ const currentTime = now();
776
+ const timeSinceLastWake = currentTime - lastWakeTime;
777
+ const timeSinceLastCall = currentTime - lastCallTime;
778
+ const timeUntilNextCall = Math.max(interval - timeSinceLastCall, 0);
779
+ lastWakeTime = currentTime;
780
+
781
+ // debounce multiple calls to wake within the `minInterval`
782
+ if (timeSinceLastWake < minInterval) {
783
+ return;
784
+ }
785
+
786
+ // reschedule a call as soon as possible, ensuring the call never happens
787
+ // faster than the `minInterval`
788
+ if (timeUntilNextCall > minInterval) {
789
+ reschedule(minInterval);
790
+ }
791
+ }
792
+
793
+ function stop() {
794
+ stopped = true;
795
+ if (timerId) {
796
+ clearTimeout(timerId);
797
+ timerId = null;
798
+ }
799
+
800
+ lastCallTime = 0;
801
+ lastWakeTime = 0;
802
+ }
803
+
804
+ function reschedule(ms) {
805
+ if (stopped) return;
806
+ clearTimeout(timerId);
807
+ timerId = setTimeout(executeAndReschedule, ms || interval);
808
+ }
809
+
810
+ function executeAndReschedule() {
811
+ lastWakeTime = 0;
812
+ lastCallTime = now();
813
+ fn(err => {
814
+ if (err) throw err;
815
+ reschedule(interval);
816
+ });
817
+ }
818
+
819
+ if (immediate) {
820
+ executeAndReschedule();
821
+ } else {
822
+ lastCallTime = now();
823
+ reschedule();
824
+ }
825
+
826
+ return { wake, stop };
827
+ }
828
+
737
829
  module.exports = {
738
830
  filterOptions,
739
831
  mergeOptions,
@@ -764,5 +856,8 @@ module.exports = {
764
856
  resolveReadPreference,
765
857
  emitDeprecationWarning,
766
858
  makeCounter,
767
- maybePromise
859
+ maybePromise,
860
+ now,
861
+ calculateDurationInMs,
862
+ makeInterruptableAsyncInterval
768
863
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongodb",
3
- "version": "3.5.8",
3
+ "version": "3.5.9",
4
4
  "description": "The official MongoDB driver for Node.js",
5
5
  "main": "index.js",
6
6
  "files": [