mongodb 3.5.7 → 3.5.11

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 (42) hide show
  1. package/HISTORY.md +44 -0
  2. package/lib/bulk/common.js +4 -11
  3. package/lib/bulk/unordered.js +8 -0
  4. package/lib/change_stream.js +194 -145
  5. package/lib/cmap/connection.js +8 -4
  6. package/lib/cmap/connection_pool.js +11 -3
  7. package/lib/collection.js +2 -3
  8. package/lib/core/connection/apm.js +1 -1
  9. package/lib/core/connection/pool.js +2 -1
  10. package/lib/core/cursor.js +46 -59
  11. package/lib/core/error.js +11 -4
  12. package/lib/core/index.js +0 -1
  13. package/lib/core/sdam/monitor.js +53 -61
  14. package/lib/core/sdam/server.js +5 -2
  15. package/lib/core/sdam/server_description.js +12 -1
  16. package/lib/core/sdam/server_selection.js +25 -37
  17. package/lib/core/sdam/topology.js +4 -26
  18. package/lib/core/sessions.js +12 -6
  19. package/lib/core/topologies/read_preference.js +58 -6
  20. package/lib/core/topologies/replset.js +4 -4
  21. package/lib/core/utils.js +13 -23
  22. package/lib/core/wireprotocol/command.js +1 -6
  23. package/lib/core/wireprotocol/get_more.js +6 -1
  24. package/lib/cursor.js +10 -32
  25. package/lib/db.js +3 -3
  26. package/lib/error.js +26 -33
  27. package/lib/mongo_client.js +8 -0
  28. package/lib/operations/collection_ops.js +1 -2
  29. package/lib/operations/command.js +2 -3
  30. package/lib/operations/command_v2.js +7 -6
  31. package/lib/operations/connect.js +11 -0
  32. package/lib/operations/cursor_ops.js +2 -3
  33. package/lib/operations/db_ops.js +1 -2
  34. package/lib/operations/find.js +2 -2
  35. package/lib/operations/geo_haystack_search.js +2 -2
  36. package/lib/operations/map_reduce.js +2 -2
  37. package/lib/operations/operation.js +2 -1
  38. package/lib/operations/run_command.js +19 -0
  39. package/lib/topologies/native_topology.js +12 -2
  40. package/lib/topologies/topology_base.js +4 -4
  41. package/lib/utils.js +96 -60
  42. package/package.json +7 -4
@@ -198,6 +198,10 @@ class ConnectionPool extends EventEmitter {
198
198
  return this[kConnections].length;
199
199
  }
200
200
 
201
+ get waitQueueSize() {
202
+ return this[kWaitQueue].length;
203
+ }
204
+
201
205
  /**
202
206
  * Check a connection out of this pool. The connection will continue to be tracked, but no reference to it
203
207
  * will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
@@ -295,7 +299,7 @@ class ConnectionPool extends EventEmitter {
295
299
  this[kCancellationToken].emit('cancel');
296
300
 
297
301
  // drain the wait queue
298
- while (this[kWaitQueue].length) {
302
+ while (this.waitQueueSize) {
299
303
  const waitQueueMember = this[kWaitQueue].pop();
300
304
  clearTimeout(waitQueueMember.timer);
301
305
  if (!waitQueueMember[kCancelled]) {
@@ -449,13 +453,17 @@ function processWaitQueue(pool) {
449
453
  return;
450
454
  }
451
455
 
452
- while (pool[kWaitQueue].length && pool.availableConnectionCount) {
456
+ while (pool.waitQueueSize) {
453
457
  const waitQueueMember = pool[kWaitQueue].peekFront();
454
458
  if (waitQueueMember[kCancelled]) {
455
459
  pool[kWaitQueue].shift();
456
460
  continue;
457
461
  }
458
462
 
463
+ if (!pool.availableConnectionCount) {
464
+ break;
465
+ }
466
+
459
467
  const connection = pool[kConnections].shift();
460
468
  const isStale = connectionIsStale(pool, connection);
461
469
  const isIdle = connectionIsIdle(pool, connection);
@@ -472,7 +480,7 @@ function processWaitQueue(pool) {
472
480
  }
473
481
 
474
482
  const maxPoolSize = pool.options.maxPoolSize;
475
- if (pool[kWaitQueue].length && (maxPoolSize <= 0 || pool.totalConnectionCount < maxPoolSize)) {
483
+ if (pool.waitQueueSize && (maxPoolSize <= 0 || pool.totalConnectionCount < maxPoolSize)) {
476
484
  createConnection(pool, (err, connection) => {
477
485
  const waitQueueMember = pool[kWaitQueue].shift();
478
486
  if (waitQueueMember == null) {
package/lib/collection.js CHANGED
@@ -16,7 +16,6 @@ const unordered = require('./bulk/unordered');
16
16
  const ordered = require('./bulk/ordered');
17
17
  const ChangeStream = require('./change_stream');
18
18
  const executeLegacyOperation = require('./utils').executeLegacyOperation;
19
- const resolveReadPreference = require('./utils').resolveReadPreference;
20
19
  const WriteConcern = require('./write_concern');
21
20
  const ReadConcern = require('./read_concern');
22
21
  const MongoDBNamespace = require('./utils').MongoDBNamespace;
@@ -399,7 +398,7 @@ Collection.prototype.find = deprecateOptions(
399
398
  newOptions.slaveOk = options.slaveOk != null ? options.slaveOk : this.s.db.slaveOk;
400
399
 
401
400
  // Add read preference if needed
402
- newOptions.readPreference = resolveReadPreference(this, newOptions);
401
+ newOptions.readPreference = ReadPreference.resolve(this, newOptions);
403
402
 
404
403
  // Set slave ok to true if read preference different from primary
405
404
  if (
@@ -1978,7 +1977,7 @@ Collection.prototype.parallelCollectionScan = deprecate(function(options, callba
1978
1977
 
1979
1978
  options = Object.assign({}, options);
1980
1979
  // Ensure we have the right read preference inheritance
1981
- options.readPreference = resolveReadPreference(this, options);
1980
+ options.readPreference = ReadPreference.resolve(this, options);
1982
1981
 
1983
1982
  // Add a promiseLibrary
1984
1983
  options.promiseLibrary = this.s.promiseLibrary;
@@ -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(
@@ -4,7 +4,6 @@ const Logger = require('./connection/logger');
4
4
  const retrieveBSON = require('./connection/utils').retrieveBSON;
5
5
  const MongoError = require('./error').MongoError;
6
6
  const MongoNetworkError = require('./error').MongoNetworkError;
7
- const mongoErrorContextSymbol = require('./error').mongoErrorContextSymbol;
8
7
  const collationNotSupported = require('./utils').collationNotSupported;
9
8
  const ReadPreference = require('./topologies/read_preference');
10
9
  const isUnifiedTopology = require('./utils').isUnifiedTopology;
@@ -412,7 +411,15 @@ class CoreCursor extends Readable {
412
411
  batchSize = this.cursorState.limit - this.cursorState.currentLimit;
413
412
  }
414
413
 
415
- this.server.getMore(this.ns, this.cursorState, batchSize, this.options, callback);
414
+ const cursorState = this.cursorState;
415
+ this.server.getMore(this.ns, cursorState, batchSize, this.options, (err, result, conn) => {
416
+ // NOTE: `getMore` modifies `cursorState`, would be very ideal not to do so in the future
417
+ if (err || (cursorState.cursorId && cursorState.cursorId.isZero())) {
418
+ this._endSession();
419
+ }
420
+
421
+ callback(err, result, conn);
422
+ });
416
423
  }
417
424
 
418
425
  _initializeCursor(callback) {
@@ -433,18 +440,15 @@ class CoreCursor extends Readable {
433
440
  }
434
441
 
435
442
  function done(err, result) {
436
- if (
437
- cursor.cursorState.cursorId &&
438
- cursor.cursorState.cursorId.isZero() &&
439
- cursor._endSession
440
- ) {
443
+ const cursorState = cursor.cursorState;
444
+ if (err || (cursorState.cursorId && cursorState.cursorId.isZero())) {
441
445
  cursor._endSession();
442
446
  }
443
447
 
444
448
  if (
445
- cursor.cursorState.documents.length === 0 &&
446
- cursor.cursorState.cursorId &&
447
- cursor.cursorState.cursorId.isZero() &&
449
+ cursorState.documents.length === 0 &&
450
+ cursorState.cursorId &&
451
+ cursorState.cursorId.isZero() &&
448
452
  !cursor.cmd.tailable &&
449
453
  !cursor.cmd.awaitData
450
454
  ) {
@@ -460,50 +464,41 @@ class CoreCursor extends Readable {
460
464
  }
461
465
 
462
466
  const result = r.message;
463
- if (result.queryFailure) {
464
- return done(new MongoError(result.documents[0]), null);
465
- }
466
467
 
467
- // Check if we have a command cursor
468
- if (
469
- Array.isArray(result.documents) &&
470
- result.documents.length === 1 &&
471
- (!cursor.cmd.find || (cursor.cmd.find && cursor.cmd.virtual === false)) &&
472
- (typeof result.documents[0].cursor !== 'string' ||
473
- result.documents[0]['$err'] ||
474
- result.documents[0]['errmsg'] ||
475
- Array.isArray(result.documents[0].result))
476
- ) {
477
- // We have an error document, return the error
478
- if (result.documents[0]['$err'] || result.documents[0]['errmsg']) {
479
- return done(new MongoError(result.documents[0]), null);
468
+ if (Array.isArray(result.documents) && result.documents.length === 1) {
469
+ const document = result.documents[0];
470
+
471
+ if (result.queryFailure) {
472
+ return done(new MongoError(document), null);
480
473
  }
481
474
 
482
- // We have a cursor document
483
- if (result.documents[0].cursor != null && typeof result.documents[0].cursor !== 'string') {
484
- const id = result.documents[0].cursor.id;
485
- // If we have a namespace change set the new namespace for getmores
486
- if (result.documents[0].cursor.ns) {
487
- cursor.ns = result.documents[0].cursor.ns;
475
+ // Check if we have a command cursor
476
+ if (!cursor.cmd.find || (cursor.cmd.find && cursor.cmd.virtual === false)) {
477
+ // We have an error document, return the error
478
+ if (document.$err || document.errmsg) {
479
+ return done(new MongoError(document), null);
488
480
  }
489
- // Promote id to long if needed
490
- cursor.cursorState.cursorId = typeof id === 'number' ? Long.fromNumber(id) : id;
491
- cursor.cursorState.lastCursorId = cursor.cursorState.cursorId;
492
- cursor.cursorState.operationTime = result.documents[0].operationTime;
493
-
494
- // If we have a firstBatch set it
495
- if (Array.isArray(result.documents[0].cursor.firstBatch)) {
496
- cursor.cursorState.documents = result.documents[0].cursor.firstBatch; //.reverse();
497
- }
498
-
499
- // Return after processing command cursor
500
- return done(null, result);
501
- }
502
481
 
503
- if (Array.isArray(result.documents[0].result)) {
504
- cursor.cursorState.documents = result.documents[0].result;
505
- cursor.cursorState.cursorId = Long.ZERO;
506
- return done(null, result);
482
+ // We have a cursor document
483
+ if (document.cursor != null && typeof document.cursor !== 'string') {
484
+ const id = document.cursor.id;
485
+ // If we have a namespace change set the new namespace for getmores
486
+ if (document.cursor.ns) {
487
+ cursor.ns = document.cursor.ns;
488
+ }
489
+ // Promote id to long if needed
490
+ cursor.cursorState.cursorId = typeof id === 'number' ? Long.fromNumber(id) : id;
491
+ cursor.cursorState.lastCursorId = cursor.cursorState.cursorId;
492
+ cursor.cursorState.operationTime = document.operationTime;
493
+
494
+ // If we have a firstBatch set it
495
+ if (Array.isArray(document.cursor.firstBatch)) {
496
+ cursor.cursorState.documents = document.cursor.firstBatch; //.reverse();
497
+ }
498
+
499
+ // Return after processing command cursor
500
+ return done(null, result);
501
+ }
507
502
  }
508
503
  }
509
504
 
@@ -690,8 +685,8 @@ function _setCursorNotifiedImpl(self, callback) {
690
685
  self.cursorState.documents = [];
691
686
  self.cursorState.cursorIndex = 0;
692
687
 
693
- if (self._endSession) {
694
- self._endSession(undefined, () => callback());
688
+ if (self.cursorState.session) {
689
+ self._endSession(callback);
695
690
  return;
696
691
  }
697
692
 
@@ -774,17 +769,9 @@ function nextFunction(self, callback) {
774
769
  // Execute the next get more
775
770
  self._getMore(function(err, doc, connection) {
776
771
  if (err) {
777
- if (err instanceof MongoError) {
778
- err[mongoErrorContextSymbol].isGetMore = true;
779
- }
780
-
781
772
  return handleCallback(callback, err);
782
773
  }
783
774
 
784
- if (self.cursorState.cursorId && self.cursorState.cursorId.isZero() && self._endSession) {
785
- self._endSession();
786
- }
787
-
788
775
  // Save the returned connection to ensure all getMore's fire over the same connection
789
776
  self.connection = connection;
790
777
 
package/lib/core/error.js CHANGED
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const mongoErrorContextSymbol = Symbol('mongoErrorContextSymbol');
4
-
5
3
  /**
6
4
  * Creates a new MongoError
7
5
  *
@@ -21,6 +19,10 @@ class MongoError extends Error {
21
19
  } else {
22
20
  super(message.message || message.errmsg || message.$err || 'n/a');
23
21
  for (var name in message) {
22
+ if (name === 'errmsg') {
23
+ continue;
24
+ }
25
+
24
26
  this[name] = message[name];
25
27
  }
26
28
  }
@@ -29,7 +31,13 @@ class MongoError extends Error {
29
31
  }
30
32
 
31
33
  this.name = 'MongoError';
32
- this[mongoErrorContextSymbol] = this[mongoErrorContextSymbol] || {};
34
+ }
35
+
36
+ /**
37
+ * Legacy name for server error responses
38
+ */
39
+ get errmsg() {
40
+ return this.message;
33
41
  }
34
42
 
35
43
  /**
@@ -262,7 +270,6 @@ module.exports = {
262
270
  MongoTimeoutError,
263
271
  MongoServerSelectionError,
264
272
  MongoWriteConcernError,
265
- mongoErrorContextSymbol,
266
273
  isRetryableError,
267
274
  isSDAMUnrecoverableError,
268
275
  isNodeShuttingDownError,
package/lib/core/index.js CHANGED
@@ -22,7 +22,6 @@ module.exports = {
22
22
  MongoTimeoutError: require('./error').MongoTimeoutError,
23
23
  MongoServerSelectionError: require('./error').MongoServerSelectionError,
24
24
  MongoWriteConcernError: require('./error').MongoWriteConcernError,
25
- mongoErrorContextSymbol: require('./error').mongoErrorContextSymbol,
26
25
  // Core
27
26
  Connection: require('./connection/connection'),
28
27
  Server: require('./topologies/server'),
@@ -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
  };
@@ -60,13 +66,12 @@ class Monitor extends EventEmitter {
60
66
  });
61
67
 
62
68
  // TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration
63
- const addressParts = server.description.address.split(':');
64
69
  this.connectOptions = Object.freeze(
65
70
  Object.assign(
66
71
  {
67
72
  id: '<monitor>',
68
- host: addressParts[0],
69
- port: parseInt(addressParts[1], 10),
73
+ host: server.description.host,
74
+ port: server.description.port,
70
75
  bson: server.s.bson,
71
76
  connectionType: Connection
72
77
  },
@@ -89,7 +94,14 @@ class Monitor extends EventEmitter {
89
94
  return;
90
95
  }
91
96
 
92
- monitorServer(this);
97
+ // start
98
+ const heartbeatFrequencyMS = this.options.heartbeatFrequencyMS;
99
+ const minHeartbeatFrequencyMS = this.options.minHeartbeatFrequencyMS;
100
+ this[kMonitorId] = makeInterruptableAsyncInterval(monitorServer(this), {
101
+ interval: heartbeatFrequencyMS,
102
+ minInterval: minHeartbeatFrequencyMS,
103
+ immediate: true
104
+ });
93
105
  }
94
106
 
95
107
  requestCheck() {
@@ -97,31 +109,19 @@ class Monitor extends EventEmitter {
97
109
  return;
98
110
  }
99
111
 
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);
112
+ this[kMonitorId].wake();
114
113
  }
115
114
 
116
115
  close() {
117
- if (this.s.state === STATE_CLOSED || this.s.state === STATE_CLOSING) {
116
+ if (isInCloseState(this)) {
118
117
  return;
119
118
  }
120
119
 
121
120
  stateTransition(this, STATE_CLOSING);
122
121
  this[kCancellationToken].emit('cancel');
123
122
  if (this[kMonitorId]) {
124
- clearTimeout(this[kMonitorId]);
123
+ this[kMonitorId].stop();
124
+ this[kMonitorId] = null;
125
125
  }
126
126
 
127
127
  if (this[kConnection]) {
@@ -138,7 +138,7 @@ function checkServer(monitor, callback) {
138
138
  monitor[kConnection] = undefined;
139
139
  }
140
140
 
141
- const start = process.hrtime();
141
+ const start = now();
142
142
  monitor.emit('serverHeartbeatStarted', new ServerHeartbeatStartedEvent(monitor.address));
143
143
 
144
144
  function failureHandler(err) {
@@ -186,7 +186,7 @@ function checkServer(monitor, callback) {
186
186
  return;
187
187
  }
188
188
 
189
- if (monitor.s.state === STATE_CLOSING || monitor.s.state === STATE_CLOSED) {
189
+ if (isInCloseState(monitor)) {
190
190
  conn.destroy({ force: true });
191
191
  failureHandler(new MongoError('monitor was destroyed'));
192
192
  return;
@@ -198,52 +198,44 @@ function checkServer(monitor, callback) {
198
198
  }
199
199
 
200
200
  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]));
201
+ return callback => {
202
+ stateTransition(monitor, STATE_MONITORING);
203
+ function done() {
204
+ if (!isInCloseState(monitor)) {
205
+ stateTransition(monitor, STATE_IDLE);
206
+ }
205
207
 
206
- checkServer(monitor, e0 => {
207
- if (e0 == null) {
208
- rescheduleMonitoring(monitor);
209
- return;
208
+ callback();
210
209
  }
211
210
 
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
- }
211
+ // TODO: the next line is a legacy event, remove in v4
212
+ process.nextTick(() => monitor.emit('monitoring', monitor[kServer]));
218
213
 
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);
214
+ checkServer(monitor, e0 => {
215
+ if (e0 == null) {
216
+ return done();
227
217
  }
228
218
 
229
- rescheduleMonitoring(monitor);
230
- });
231
- });
232
- }
219
+ // otherwise an error occured on initial discovery, also bail
220
+ if (monitor[kServer].description.type === ServerType.Unknown) {
221
+ monitor.emit('resetServer', e0);
222
+ return done();
223
+ }
233
224
 
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
- }
225
+ // According to the SDAM specification's "Network error during server check" section, if
226
+ // an ismaster call fails we reset the server's pool. If a server was once connected,
227
+ // change its type to `Unknown` only after retrying once.
228
+ monitor.emit('resetConnectionPool');
239
229
 
240
- stateTransition(monitor, STATE_IDLE);
230
+ checkServer(monitor, e1 => {
231
+ if (e1) {
232
+ monitor.emit('resetServer', e1);
233
+ }
241
234
 
242
- monitor[kLastCheckTime] = process.hrtime();
243
- monitor[kMonitorId] = setTimeout(() => {
244
- monitor[kMonitorId] = undefined;
245
- monitor.requestCheck();
246
- }, ms || heartbeatFrequencyMS);
235
+ done();
236
+ });
237
+ });
238
+ };
247
239
  }
248
240
 
249
241
  module.exports = {
@@ -110,9 +110,8 @@ class Server extends EventEmitter {
110
110
 
111
111
  // create the connection pool
112
112
  // NOTE: this used to happen in `connect`, we supported overriding pool options there
113
- const addressParts = this.description.address.split(':');
114
113
  const poolOptions = Object.assign(
115
- { host: addressParts[0], port: parseInt(addressParts[1], 10), bson: this.s.bson },
114
+ { host: this.description.host, port: this.description.port, bson: this.s.bson },
116
115
  options
117
116
  );
118
117
 
@@ -415,6 +414,10 @@ Object.defineProperty(Server.prototype, 'clusterTime', {
415
414
  });
416
415
 
417
416
  function calculateRoundTripTime(oldRtt, duration) {
417
+ if (oldRtt === -1) {
418
+ return duration;
419
+ }
420
+
418
421
  const alpha = 0.2;
419
422
  return alpha * duration + (1 - alpha) * oldRtt;
420
423
  }
@@ -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);
@@ -112,6 +113,16 @@ class ServerDescription {
112
113
  return WRITABLE_SERVER_TYPES.has(this.type);
113
114
  }
114
115
 
116
+ get host() {
117
+ const chopLength = `:${this.port}`.length;
118
+ return this.address.slice(0, -chopLength);
119
+ }
120
+
121
+ get port() {
122
+ const port = this.address.split(':').pop();
123
+ return port ? Number.parseInt(port, 10) : port;
124
+ }
125
+
115
126
  /**
116
127
  * Determines if another `ServerDescription` is equal to this one per the rules defined
117
128
  * in the {@link https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#serverdescription|SDAM spec}
@@ -48,7 +48,7 @@ function maxStalenessReducer(readPreference, topologyDescription, servers) {
48
48
  }
49
49
 
50
50
  if (topologyDescription.type === TopologyType.ReplicaSetWithPrimary) {
51
- const primary = servers.filter(primaryFilter)[0];
51
+ const primary = Array.from(topologyDescription.servers.values()).filter(primaryFilter)[0];
52
52
  return servers.reduce((result, server) => {
53
53
  const stalenessMS =
54
54
  server.lastUpdateTime -
@@ -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 =
@@ -197,50 +203,32 @@ function readPreferenceServerSelector(readPreference) {
197
203
  return latencyWindowReducer(topologyDescription, servers.filter(knownFilter));
198
204
  }
199
205
 
200
- if (readPreference.mode === ReadPreference.PRIMARY) {
206
+ const mode = readPreference.mode;
207
+ if (mode === ReadPreference.PRIMARY) {
201
208
  return servers.filter(primaryFilter);
202
209
  }
203
210
 
204
- if (readPreference.mode === ReadPreference.SECONDARY) {
205
- return latencyWindowReducer(
206
- topologyDescription,
207
- tagSetReducer(
208
- readPreference,
209
- maxStalenessReducer(readPreference, topologyDescription, servers)
210
- )
211
- ).filter(secondaryFilter);
212
- } else if (readPreference.mode === ReadPreference.NEAREST) {
213
- return latencyWindowReducer(
214
- topologyDescription,
215
- tagSetReducer(
216
- readPreference,
217
- maxStalenessReducer(readPreference, topologyDescription, servers)
218
- )
219
- ).filter(nearestFilter);
220
- } else if (readPreference.mode === ReadPreference.SECONDARY_PREFERRED) {
221
- const result = latencyWindowReducer(
222
- topologyDescription,
223
- tagSetReducer(
224
- readPreference,
225
- maxStalenessReducer(readPreference, topologyDescription, servers)
226
- )
227
- ).filter(secondaryFilter);
228
-
229
- return result.length === 0 ? servers.filter(primaryFilter) : result;
230
- } else if (readPreference.mode === ReadPreference.PRIMARY_PREFERRED) {
211
+ if (mode === ReadPreference.PRIMARY_PREFERRED) {
231
212
  const result = servers.filter(primaryFilter);
232
213
  if (result.length) {
233
214
  return result;
234
215
  }
216
+ }
235
217
 
236
- return latencyWindowReducer(
237
- topologyDescription,
238
- tagSetReducer(
239
- readPreference,
240
- maxStalenessReducer(readPreference, topologyDescription, servers)
241
- )
242
- ).filter(secondaryFilter);
218
+ const filter = mode === ReadPreference.NEAREST ? nearestFilter : secondaryFilter;
219
+ const selectedServers = latencyWindowReducer(
220
+ topologyDescription,
221
+ tagSetReducer(
222
+ readPreference,
223
+ maxStalenessReducer(readPreference, topologyDescription, servers.filter(filter))
224
+ )
225
+ );
226
+
227
+ if (mode === ReadPreference.SECONDARY_PREFERRED && selectedServers.length === 0) {
228
+ return servers.filter(primaryFilter);
243
229
  }
230
+
231
+ return selectedServers;
244
232
  };
245
233
  }
246
234