mongodb 3.3.3 → 3.4.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 (51) hide show
  1. package/HISTORY.md +90 -0
  2. package/index.js +4 -0
  3. package/lib/bulk/common.js +26 -5
  4. package/lib/bulk/ordered.js +9 -4
  5. package/lib/bulk/unordered.js +9 -4
  6. package/lib/collection.js +123 -35
  7. package/lib/core/auth/scram.js +42 -6
  8. package/lib/core/cmap/connection.js +220 -0
  9. package/lib/core/cmap/message_stream.js +181 -0
  10. package/lib/core/connection/apm.js +14 -16
  11. package/lib/core/connection/connect.js +56 -39
  12. package/lib/core/connection/connection.js +17 -1
  13. package/lib/core/connection/logger.js +9 -4
  14. package/lib/core/connection/msg.js +1 -1
  15. package/lib/core/connection/pool.js +123 -86
  16. package/lib/core/cursor.js +1 -1
  17. package/lib/core/error.js +17 -6
  18. package/lib/core/index.js +1 -1
  19. package/lib/core/sdam/common.js +59 -0
  20. package/lib/core/sdam/monitoring.js +16 -10
  21. package/lib/core/sdam/server.js +79 -36
  22. package/lib/core/sdam/server_description.js +30 -14
  23. package/lib/core/sdam/{server_selectors.js → server_selection.js} +100 -7
  24. package/lib/core/sdam/srv_polling.js +1 -1
  25. package/lib/core/sdam/topology.js +165 -192
  26. package/lib/core/sdam/topology_description.js +14 -23
  27. package/lib/core/sessions.js +5 -8
  28. package/lib/core/topologies/replset.js +26 -15
  29. package/lib/core/topologies/server.js +8 -12
  30. package/lib/core/topologies/shared.js +26 -16
  31. package/lib/core/uri_parser.js +49 -1
  32. package/lib/core/utils.js +32 -1
  33. package/lib/core/wireprotocol/command.js +25 -3
  34. package/lib/core/wireprotocol/compression.js +13 -13
  35. package/lib/core/wireprotocol/shared.js +1 -1
  36. package/lib/db.js +3 -1
  37. package/lib/gridfs/grid_store.js +15 -8
  38. package/lib/gridfs-stream/download.js +5 -4
  39. package/lib/gridfs-stream/index.js +3 -2
  40. package/lib/gridfs-stream/upload.js +3 -3
  41. package/lib/mongo_client.js +44 -34
  42. package/lib/operations/close.js +6 -2
  43. package/lib/operations/command_v2.js +1 -3
  44. package/lib/operations/common_functions.js +5 -2
  45. package/lib/operations/connect.js +48 -3
  46. package/lib/operations/create_collection.js +1 -2
  47. package/lib/operations/db_ops.js +2 -4
  48. package/lib/operations/execute_operation.js +2 -1
  49. package/lib/operations/map_reduce.js +2 -1
  50. package/lib/utils.js +2 -1
  51. package/package.json +8 -5
@@ -12,14 +12,19 @@ var pid = process.pid;
12
12
  // current logger
13
13
  var currentLogger = null;
14
14
 
15
+ /**
16
+ * @callback Logger~loggerCallback
17
+ * @param {string} msg message being logged
18
+ * @param {object} state an object containing more metadata about the logging message
19
+ */
20
+
15
21
  /**
16
22
  * Creates a new Logger instance
17
23
  * @class
18
24
  * @param {string} className The Class name associated with the logging instance
19
25
  * @param {object} [options=null] Optional settings.
20
- * @param {Function} [options.logger=null] Custom logger function;
26
+ * @param {Logger~loggerCallback} [options.logger=null] Custom logger function;
21
27
  * @param {string} [options.loggerLevel=error] Override default global log level.
22
- * @return {Logger} a Logger instance.
23
28
  */
24
29
  var Logger = function(className, options) {
25
30
  if (!(this instanceof Logger)) return new Logger(className, options);
@@ -195,7 +200,7 @@ Logger.reset = function() {
195
200
  /**
196
201
  * Get the current logger function
197
202
  * @method
198
- * @return {function}
203
+ * @return {Logger~loggerCallback}
199
204
  */
200
205
  Logger.currentLogger = function() {
201
206
  return currentLogger;
@@ -204,7 +209,7 @@ Logger.currentLogger = function() {
204
209
  /**
205
210
  * Set the current logger function
206
211
  * @method
207
- * @param {function} logger Logger function.
212
+ * @param {Logger~loggerCallback} logger Logger function.
208
213
  * @return {null}
209
214
  */
210
215
  Logger.setCurrentLogger = function(logger) {
@@ -59,7 +59,7 @@ class Msg {
59
59
  this.options = options || {};
60
60
 
61
61
  // Additional options
62
- this.requestId = Msg.getRequestId();
62
+ this.requestId = options.requestId ? options.requestId : Msg.getRequestId();
63
63
 
64
64
  // Serialization option
65
65
  this.serializeFunctions =
@@ -20,12 +20,22 @@ const Buffer = require('safe-buffer').Buffer;
20
20
  const connect = require('./connect');
21
21
  const updateSessionFromResponse = require('../sessions').updateSessionFromResponse;
22
22
  const eachAsync = require('../utils').eachAsync;
23
-
24
- var DISCONNECTED = 'disconnected';
25
- var CONNECTING = 'connecting';
26
- var CONNECTED = 'connected';
27
- var DESTROYING = 'destroying';
28
- var DESTROYED = 'destroyed';
23
+ const makeStateMachine = require('../utils').makeStateMachine;
24
+
25
+ const DISCONNECTED = 'disconnected';
26
+ const CONNECTING = 'connecting';
27
+ const CONNECTED = 'connected';
28
+ const DRAINING = 'draining';
29
+ const DESTROYING = 'destroying';
30
+ const DESTROYED = 'destroyed';
31
+ const stateTransition = makeStateMachine({
32
+ [DISCONNECTED]: [CONNECTING, DRAINING, DISCONNECTED],
33
+ [CONNECTING]: [CONNECTING, CONNECTED, DRAINING, DISCONNECTED],
34
+ [CONNECTED]: [CONNECTED, DISCONNECTED, DRAINING],
35
+ [DRAINING]: [DRAINING, DESTROYING, DESTROYED],
36
+ [DESTROYING]: [DESTROYING, DESTROYED],
37
+ [DESTROYED]: [DESTROYED]
38
+ });
29
39
 
30
40
  const CONNECTION_EVENTS = new Set([
31
41
  'error',
@@ -80,6 +90,14 @@ var Pool = function(topology, options) {
80
90
  // Store topology for later use
81
91
  this.topology = topology;
82
92
 
93
+ this.s = {
94
+ state: DISCONNECTED,
95
+ cancellationToken: new EventEmitter()
96
+ };
97
+
98
+ // we don't care how many connections are listening for cancellation
99
+ this.s.cancellationToken.setMaxListeners(Infinity);
100
+
83
101
  // Add the options
84
102
  this.options = Object.assign(
85
103
  {
@@ -138,8 +156,6 @@ var Pool = function(topology, options) {
138
156
 
139
157
  // Logger instance
140
158
  this.logger = Logger('Pool', options);
141
- // Pool state
142
- this.state = DISCONNECTED;
143
159
  // Connections
144
160
  this.availableConnections = [];
145
161
  this.inUseConnections = [];
@@ -208,6 +224,13 @@ Object.defineProperty(Pool.prototype, 'socketTimeout', {
208
224
  }
209
225
  });
210
226
 
227
+ Object.defineProperty(Pool.prototype, 'state', {
228
+ enumerable: true,
229
+ get: function() {
230
+ return this.s.state;
231
+ }
232
+ });
233
+
211
234
  // clears all pool state
212
235
  function resetPoolState(pool) {
213
236
  pool.inUseConnections = [];
@@ -220,47 +243,36 @@ function resetPoolState(pool) {
220
243
  pool.reconnectId = null;
221
244
  }
222
245
 
223
- function stateTransition(self, newState) {
224
- var legalTransitions = {
225
- disconnected: [CONNECTING, DESTROYING, DISCONNECTED],
226
- connecting: [CONNECTING, DESTROYING, CONNECTED, DISCONNECTED],
227
- connected: [CONNECTED, DISCONNECTED, DESTROYING],
228
- destroying: [DESTROYING, DESTROYED],
229
- destroyed: [DESTROYED]
230
- };
231
-
232
- // Get current state
233
- var legalStates = legalTransitions[self.state];
234
- if (legalStates && legalStates.indexOf(newState) !== -1) {
235
- self.emit('stateChanged', self.state, newState);
236
- self.state = newState;
237
- } else {
238
- self.logger.error(
239
- f(
240
- 'Pool with id [%s] failed attempted illegal state transition from [%s] to [%s] only following state allowed [%s]',
241
- self.id,
242
- self.state,
243
- newState,
244
- legalStates
245
- )
246
- );
247
- }
248
- }
249
-
250
246
  function connectionFailureHandler(pool, event, err, conn) {
251
247
  if (conn) {
252
- if (conn._connectionFailHandled) return;
248
+ if (conn._connectionFailHandled) {
249
+ return;
250
+ }
251
+
253
252
  conn._connectionFailHandled = true;
254
253
  conn.destroy();
255
254
 
256
255
  // Remove the connection
257
256
  removeConnection(pool, conn);
258
257
 
259
- // Flush all work Items on this connection
260
- while (conn.workItems.length > 0) {
261
- const workItem = conn.workItems.shift();
262
- if (workItem.cb) workItem.cb(err);
258
+ if (
259
+ pool.state !== DRAINING &&
260
+ pool.state !== DESTROYED &&
261
+ pool.options.legacyCompatMode === false
262
+ ) {
263
+ // since an error/close/timeout means pool invalidation in a
264
+ // pre-CMAP world, we will issue a custom `drain` event here to
265
+ // signal that the server should be recycled
266
+ stateTransition(pool, DRAINING);
267
+ pool.emit('drain', err);
268
+
269
+ // wait to flush work items so this server isn't selected again immediately
270
+ process.nextTick(() => conn.flush(err));
271
+ return;
263
272
  }
273
+
274
+ // flush remaining work items
275
+ conn.flush(err);
264
276
  }
265
277
 
266
278
  // Did we catch a timeout, increment the numberOfConsecutiveTimeouts
@@ -280,8 +292,10 @@ function connectionFailureHandler(pool, event, err, conn) {
280
292
 
281
293
  // No more socket available propegate the event
282
294
  if (pool.socketCount() === 0) {
283
- if (pool.state !== DESTROYED && pool.state !== DESTROYING) {
284
- stateTransition(pool, DISCONNECTED);
295
+ if (pool.state !== DESTROYED && pool.state !== DESTROYING && pool.state !== DRAINING) {
296
+ if (pool.options.reconnect) {
297
+ stateTransition(pool, DISCONNECTED);
298
+ }
285
299
  }
286
300
 
287
301
  // Do not emit error events, they are always close events
@@ -320,9 +334,7 @@ function attemptReconnect(pool, callback) {
320
334
  pool.destroy();
321
335
 
322
336
  const error = new MongoTimeoutError(
323
- `failed to reconnect after ${pool.options.reconnectTries} attempts with interval ${
324
- pool.options.reconnectInterval
325
- } ms`,
337
+ `failed to reconnect after ${pool.options.reconnectTries} attempts with interval ${pool.options.reconnectInterval} ms`,
326
338
  pool.reconnectError
327
339
  );
328
340
 
@@ -436,7 +448,7 @@ function messageHandler(self) {
436
448
  updateSessionFromResponse(session, document);
437
449
  }
438
450
 
439
- if (document.$clusterTime) {
451
+ if (self.topology && document.$clusterTime) {
440
452
  self.topology.clusterTime = document.$clusterTime;
441
453
  }
442
454
  }
@@ -547,7 +559,7 @@ Pool.prototype.isDisconnected = function() {
547
559
  /**
548
560
  * Connect pool
549
561
  */
550
- Pool.prototype.connect = function() {
562
+ Pool.prototype.connect = function(callback) {
551
563
  if (this.state !== DISCONNECTED) {
552
564
  throw new MongoError('connection in unlawful state ' + this.state);
553
565
  }
@@ -555,6 +567,12 @@ Pool.prototype.connect = function() {
555
567
  stateTransition(this, CONNECTING);
556
568
  createConnection(this, (err, conn) => {
557
569
  if (err) {
570
+ if (typeof callback === 'function') {
571
+ this.destroy();
572
+ callback(err);
573
+ return;
574
+ }
575
+
558
576
  if (this.state === CONNECTING) {
559
577
  this.emit('error', err);
560
578
  }
@@ -564,7 +582,6 @@ Pool.prototype.connect = function() {
564
582
  }
565
583
 
566
584
  stateTransition(this, CONNECTED);
567
- this.emit('connect', this, conn);
568
585
 
569
586
  // create min connections
570
587
  if (this.minSize) {
@@ -572,6 +589,12 @@ Pool.prototype.connect = function() {
572
589
  createConnection(this);
573
590
  }
574
591
  }
592
+
593
+ if (typeof callback === 'function') {
594
+ callback(null, conn);
595
+ } else {
596
+ this.emit('connect', this, conn);
597
+ }
575
598
  });
576
599
  };
577
600
 
@@ -606,6 +629,11 @@ Pool.prototype.unref = function() {
606
629
 
607
630
  // Destroy the connections
608
631
  function destroy(self, connections, options, callback) {
632
+ stateTransition(self, DESTROYING);
633
+
634
+ // indicate that in-flight connections should cancel
635
+ self.s.cancellationToken.emit('cancel');
636
+
609
637
  eachAsync(
610
638
  connections,
611
639
  (conn, cb) => {
@@ -636,14 +664,19 @@ function destroy(self, connections, options, callback) {
636
664
  */
637
665
  Pool.prototype.destroy = function(force, callback) {
638
666
  var self = this;
667
+ if (typeof force === 'function') {
668
+ callback = force;
669
+ force = false;
670
+ }
671
+
639
672
  // Do not try again if the pool is already dead
640
673
  if (this.state === DESTROYED || self.state === DESTROYING) {
641
674
  if (typeof callback === 'function') callback(null, null);
642
675
  return;
643
676
  }
644
677
 
645
- // Set state to destroyed
646
- stateTransition(this, DESTROYING);
678
+ // Set state to draining
679
+ stateTransition(this, DRAINING);
647
680
 
648
681
  // Are we force closing
649
682
  if (force) {
@@ -670,6 +703,14 @@ Pool.prototype.destroy = function(force, callback) {
670
703
 
671
704
  // Wait for the operations to drain before we close the pool
672
705
  function checkStatus() {
706
+ if (self.state === DESTROYED || self.state === DESTROYING) {
707
+ if (typeof callback === 'function') {
708
+ callback();
709
+ }
710
+
711
+ return;
712
+ }
713
+
673
714
  flushMonitoringOperations(self.queue);
674
715
 
675
716
  if (self.queue.length === 0) {
@@ -686,7 +727,6 @@ Pool.prototype.destroy = function(force, callback) {
686
727
  }
687
728
 
688
729
  destroy(self, connections, { force: false }, callback);
689
- // } else if (self.queue.length > 0 && !this.reconnectId) {
690
730
  } else {
691
731
  // Ensure we empty the queue
692
732
  _execute(self)();
@@ -705,6 +745,18 @@ Pool.prototype.destroy = function(force, callback) {
705
745
  * @param {function} [callback]
706
746
  */
707
747
  Pool.prototype.reset = function(callback) {
748
+ if (this.s.state !== CONNECTED) {
749
+ if (typeof callback === 'function') {
750
+ callback(new MongoError('pool is not connected, reset aborted'));
751
+ }
752
+
753
+ return;
754
+ }
755
+
756
+ // signal in-flight connections should be cancelled
757
+ this.s.cancellationToken.emit('cancel');
758
+
759
+ // destroy existing connections
708
760
  const connections = this.availableConnections.concat(this.inUseConnections);
709
761
  eachAsync(
710
762
  connections,
@@ -725,12 +777,12 @@ Pool.prototype.reset = function(callback) {
725
777
 
726
778
  resetPoolState(this);
727
779
 
728
- // create an initial connection, and kick off execution again
729
- createConnection(this);
730
-
731
- if (typeof callback === 'function') {
732
- callback(null, null);
733
- }
780
+ // create a new connection, this will ultimately trigger execution
781
+ createConnection(this, () => {
782
+ if (typeof callback === 'function') {
783
+ callback(null, null);
784
+ }
785
+ });
734
786
  }
735
787
  );
736
788
  };
@@ -798,17 +850,12 @@ Pool.prototype.write = function(command, options, cb) {
798
850
 
799
851
  // Pool was destroyed error out
800
852
  if (this.state === DESTROYED || this.state === DESTROYING) {
801
- // Callback with an error
802
- if (cb) {
803
- try {
804
- cb(new MongoError('pool destroyed'));
805
- } catch (err) {
806
- process.nextTick(function() {
807
- throw err;
808
- });
809
- }
810
- }
853
+ cb(new MongoError('pool destroyed'));
854
+ return;
855
+ }
811
856
 
857
+ if (this.state === DRAINING) {
858
+ cb(new MongoError('pool is draining, new operations prohibited'));
812
859
  return;
813
860
  }
814
861
 
@@ -856,10 +903,6 @@ Pool.prototype.write = function(command, options, cb) {
856
903
  // Optional per operation socketTimeout
857
904
  operation.socketTimeout = options.socketTimeout;
858
905
  operation.monitoring = options.monitoring;
859
- // Custom socket Timeout
860
- if (options.socketTimeout) {
861
- operation.socketTimeout = options.socketTimeout;
862
- }
863
906
 
864
907
  // Get the requestId
865
908
  operation.requestId = command.requestId;
@@ -922,7 +965,7 @@ Pool.prototype.write = function(command, options, cb) {
922
965
  function canCompress(command) {
923
966
  const commandDoc = command instanceof Msg ? command.command : command.query;
924
967
  const commandName = Object.keys(commandDoc)[0];
925
- return uncompressibleCommands.indexOf(commandName) === -1;
968
+ return !uncompressibleCommands.has(commandName);
926
969
  }
927
970
 
928
971
  // Remove connection method
@@ -950,7 +993,7 @@ function createConnection(pool, callback) {
950
993
  }
951
994
 
952
995
  pool.connectingConnections++;
953
- connect(pool.options, (err, connection) => {
996
+ connect(pool.options, pool.s.cancellationToken, (err, connection) => {
954
997
  pool.connectingConnections--;
955
998
 
956
999
  if (err) {
@@ -958,15 +1001,6 @@ function createConnection(pool, callback) {
958
1001
  pool.logger.debug(`connection attempt failed with error [${JSON.stringify(err)}]`);
959
1002
  }
960
1003
 
961
- if (pool.options.legacyCompatMode === false) {
962
- // The unified topology uses the reported `error` from a pool to track what error
963
- // reason is returned to the user during selection timeout. We only want to emit
964
- // this if the pool is active because the listeners are removed on destruction.
965
- if (pool.state !== DESTROYED && pool.state !== DESTROYING) {
966
- pool.emit('error', err);
967
- }
968
- }
969
-
970
1004
  // check if reconnect is enabled, and attempt retry if so
971
1005
  if (!pool.reconnectId && pool.options.reconnect) {
972
1006
  if (pool.state === CONNECTING && pool.options.legacyCompatMode) {
@@ -1057,6 +1091,12 @@ function _execute(self) {
1057
1091
  if (self.availableConnections.length === 0) {
1058
1092
  // Flush any monitoring operations
1059
1093
  flushMonitoringOperations(self.queue);
1094
+
1095
+ // Try to create a new connection to execute stuck operation
1096
+ if (totalConnections < self.options.size && self.queue.length > 0) {
1097
+ createConnection(self);
1098
+ }
1099
+
1060
1100
  break;
1061
1101
  }
1062
1102
 
@@ -1121,10 +1161,7 @@ function _execute(self) {
1121
1161
  }
1122
1162
 
1123
1163
  // Re-execute the operation
1124
- setTimeout(function() {
1125
- _execute(self)();
1126
- }, 10);
1127
-
1164
+ setTimeout(() => _execute(self)(), 10);
1128
1165
  break;
1129
1166
  }
1130
1167
  }
@@ -435,7 +435,7 @@ class CoreCursor extends Readable {
435
435
  return;
436
436
  }
437
437
 
438
- cursor._next(callback);
438
+ this._initializeCursor(callback);
439
439
  });
440
440
 
441
441
  return;
package/lib/core/error.js CHANGED
@@ -44,25 +44,29 @@ class MongoError extends Error {
44
44
  return new MongoError(options);
45
45
  }
46
46
 
47
+ /**
48
+ * Checks the error to see if it has an error label
49
+ * @param {string} label The error label to check for
50
+ * @returns {boolean} returns true if the error has the provided error label
51
+ */
47
52
  hasErrorLabel(label) {
48
53
  return this.errorLabels && this.errorLabels.indexOf(label) !== -1;
49
54
  }
50
55
  }
51
56
 
52
57
  /**
53
- * Creates a new MongoNetworkError
58
+ * An error indicating an issue with the network, including TCP
59
+ * errors and timeouts.
54
60
  *
55
61
  * @param {Error|string|object} message The error message
56
62
  * @property {string} message The error message
57
63
  * @property {string} stack The error call stack
64
+ * @extends MongoError
58
65
  */
59
66
  class MongoNetworkError extends MongoError {
60
67
  constructor(message) {
61
68
  super(message);
62
69
  this.name = 'MongoNetworkError';
63
-
64
- // This is added as part of the transactions specification
65
- this.errorLabels = ['TransientTransactionError'];
66
70
  }
67
71
  }
68
72
 
@@ -71,6 +75,7 @@ class MongoNetworkError extends MongoError {
71
75
  *
72
76
  * @param {Error|string|object} message The error message
73
77
  * @property {string} message The error message
78
+ * @extends MongoError
74
79
  */
75
80
  class MongoParseError extends MongoError {
76
81
  constructor(message) {
@@ -80,12 +85,13 @@ class MongoParseError extends MongoError {
80
85
  }
81
86
 
82
87
  /**
83
- * An error signifying a timeout event
88
+ * An error signifying a client-side timeout event
84
89
  *
85
90
  * @param {Error|string|object} message The error message
86
91
  * @param {string|object} [reason] The reason the timeout occured
87
92
  * @property {string} message The error message
88
93
  * @property {string} [reason] An optional reason context for the timeout, generally an error saved during flow of monitoring and selecting servers
94
+ * @extends MongoError
89
95
  */
90
96
  class MongoTimeoutError extends MongoError {
91
97
  constructor(message, reason) {
@@ -117,6 +123,7 @@ function makeWriteConcernResultObject(input) {
117
123
  * @param {object} result The result document (provided if ok: 1)
118
124
  * @property {string} message The error message
119
125
  * @property {object} [result] The result document (provided if ok: 1)
126
+ * @extends MongoError
120
127
  */
121
128
  class MongoWriteConcernError extends MongoError {
122
129
  constructor(message, result) {
@@ -147,6 +154,7 @@ const RETRYABLE_ERROR_CODES = new Set([
147
154
  /**
148
155
  * Determines whether an error is something the driver should attempt to retry
149
156
  *
157
+ * @ignore
150
158
  * @param {MongoError|Error} error
151
159
  */
152
160
  function isRetryableError(error) {
@@ -205,12 +213,15 @@ function isNodeShuttingDownError(err) {
205
213
  * then the pool will be cleared, and server state will completely reset
206
214
  * locally.
207
215
  *
216
+ * @ignore
208
217
  * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
209
218
  * @param {MongoError|Error} error
210
219
  * @param {Server} server
211
220
  */
212
221
  function isSDAMUnrecoverableError(error, server) {
213
- if (error instanceof MongoParseError) {
222
+ // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
223
+ // close event are considered unrecoverable
224
+ if (error instanceof MongoParseError || error == null) {
214
225
  return true;
215
226
  }
216
227
 
package/lib/core/index.js CHANGED
@@ -33,7 +33,7 @@ module.exports = {
33
33
  Sessions: require('./sessions'),
34
34
  BSON: BSON,
35
35
  EJSON: EJSON,
36
- Topology: require('./sdam/topology'),
36
+ Topology: require('./sdam/topology').Topology,
37
37
  // Raw operations
38
38
  Query: require('./connection/commands').Query,
39
39
  // Auth mechanisms
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ // shared state names
4
+ const STATE_CLOSING = 'closing';
5
+ const STATE_CLOSED = 'closed';
6
+ const STATE_CONNECTING = 'connecting';
7
+ const STATE_CONNECTED = 'connected';
8
+
9
+ // An enumeration of topology types we know about
10
+ const TopologyType = {
11
+ Single: 'Single',
12
+ ReplicaSetNoPrimary: 'ReplicaSetNoPrimary',
13
+ ReplicaSetWithPrimary: 'ReplicaSetWithPrimary',
14
+ Sharded: 'Sharded',
15
+ Unknown: 'Unknown'
16
+ };
17
+
18
+ // An enumeration of server types we know about
19
+ const ServerType = {
20
+ Standalone: 'Standalone',
21
+ Mongos: 'Mongos',
22
+ PossiblePrimary: 'PossiblePrimary',
23
+ RSPrimary: 'RSPrimary',
24
+ RSSecondary: 'RSSecondary',
25
+ RSArbiter: 'RSArbiter',
26
+ RSOther: 'RSOther',
27
+ RSGhost: 'RSGhost',
28
+ Unknown: 'Unknown'
29
+ };
30
+
31
+ const TOPOLOGY_DEFAULTS = {
32
+ useUnifiedTopology: true,
33
+ localThresholdMS: 15,
34
+ serverSelectionTimeoutMS: 30000,
35
+ heartbeatFrequencyMS: 10000,
36
+ minHeartbeatFrequencyMS: 500
37
+ };
38
+
39
+ function drainTimerQueue(queue) {
40
+ queue.forEach(clearTimeout);
41
+ queue.clear();
42
+ }
43
+
44
+ function clearAndRemoveTimerFrom(timer, timers) {
45
+ clearTimeout(timer);
46
+ return timers.delete(timer);
47
+ }
48
+
49
+ module.exports = {
50
+ STATE_CLOSING,
51
+ STATE_CLOSED,
52
+ STATE_CONNECTING,
53
+ STATE_CONNECTED,
54
+ TOPOLOGY_DEFAULTS,
55
+ TopologyType,
56
+ ServerType,
57
+ drainTimerQueue,
58
+ clearAndRemoveTimerFrom
59
+ };
@@ -4,8 +4,8 @@ const ServerDescription = require('./server_description').ServerDescription;
4
4
  const calculateDurationInMs = require('../utils').calculateDurationInMs;
5
5
 
6
6
  // pulled from `Server` implementation
7
- const STATE_DISCONNECTED = 'disconnected';
8
- const STATE_DISCONNECTING = 'disconnecting';
7
+ const STATE_CLOSED = 'closed';
8
+ const STATE_CLOSING = 'closing';
9
9
 
10
10
  /**
11
11
  * Published when server description changes, but does NOT include changes to the RTT.
@@ -135,6 +135,14 @@ function monitorServer(server, options) {
135
135
  return;
136
136
  }
137
137
 
138
+ const rescheduleMonitoring = () => {
139
+ server.s.monitoring = false;
140
+ server.s.monitorId = setTimeout(() => {
141
+ server.s.monitorId = undefined;
142
+ server.monitor();
143
+ }, heartbeatFrequencyMS);
144
+ };
145
+
138
146
  // executes a single check of a server
139
147
  const checkServer = callback => {
140
148
  let start = process.hrtime();
@@ -164,6 +172,9 @@ function monitorServer(server, options) {
164
172
  return callback(err, null);
165
173
  }
166
174
 
175
+ // save round trip time
176
+ server.description.roundTripTime = duration;
177
+
167
178
  const isMaster = result.result;
168
179
  server.emit(
169
180
  'serverHeartbeatSucceeded',
@@ -176,16 +187,13 @@ function monitorServer(server, options) {
176
187
  };
177
188
 
178
189
  const successHandler = isMaster => {
179
- server.s.monitoring = false;
180
-
181
190
  // emit an event indicating that our description has changed
182
191
  server.emit('descriptionReceived', new ServerDescription(server.description.address, isMaster));
183
- if (server.s.state === STATE_DISCONNECTED || server.s.state === STATE_DISCONNECTING) {
192
+ if (server.s.state === STATE_CLOSED || server.s.state === STATE_CLOSING) {
184
193
  return;
185
194
  }
186
195
 
187
- // schedule the next monitoring process
188
- server.s.monitorId = setTimeout(() => monitorServer(server), heartbeatFrequencyMS);
196
+ rescheduleMonitoring();
189
197
  };
190
198
 
191
199
  // run the actual monitoring loop
@@ -203,15 +211,13 @@ function monitorServer(server, options) {
203
211
  // otherwise re-attempt monitoring once
204
212
  checkServer((error, isMaster) => {
205
213
  if (error) {
206
- server.s.monitoring = false;
207
-
208
214
  // we revert to an `Unknown` by emitting a default description with no isMaster
209
215
  server.emit(
210
216
  'descriptionReceived',
211
217
  new ServerDescription(server.description.address, null, { error })
212
218
  );
213
219
 
214
- // we do not reschedule monitoring in this case
220
+ rescheduleMonitoring();
215
221
  return;
216
222
  }
217
223