mongodb 3.5.5 → 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.
@@ -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
@@ -310,7 +310,7 @@ const DEPRECATED_FIND_OPTIONS = ['maxScan', 'fields', 'snapshot'];
310
310
  * @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
311
311
  * @param {boolean} [options.partial=false] Specify if the cursor should return partial results when querying against a sharded system
312
312
  * @param {number} [options.maxTimeMS] Number of milliseconds to wait before aborting the query.
313
- * @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `taiable` and `awaitData` to be true
313
+ * @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `tailable` and `awaitData` to be true
314
314
  * @param {boolean} [options.noCursorTimeout] The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that.
315
315
  * @param {object} [options.collation] Specify collation (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields).
316
316
  * @param {ClientSession} [options.session] optional session to use for this operation
@@ -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;
@@ -359,15 +358,6 @@ class CoreCursor extends Readable {
359
358
  return this.push(this.cursorState.streamOptions.transform(result));
360
359
  }
361
360
 
362
- // If we provided a map function
363
- if (
364
- this.cursorState.transforms &&
365
- typeof this.cursorState.transforms.doc === 'function' &&
366
- result != null
367
- ) {
368
- return this.push(this.cursorState.transforms.doc(result));
369
- }
370
-
371
361
  // Return the result
372
362
  this.push(result);
373
363
 
@@ -421,7 +411,15 @@ class CoreCursor extends Readable {
421
411
  batchSize = this.cursorState.limit - this.cursorState.currentLimit;
422
412
  }
423
413
 
424
- 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
+ });
425
423
  }
426
424
 
427
425
  _initializeCursor(callback) {
@@ -442,18 +440,15 @@ class CoreCursor extends Readable {
442
440
  }
443
441
 
444
442
  function done(err, result) {
445
- if (
446
- cursor.cursorState.cursorId &&
447
- cursor.cursorState.cursorId.isZero() &&
448
- cursor._endSession
449
- ) {
443
+ const cursorState = cursor.cursorState;
444
+ if (err || (cursorState.cursorId && cursorState.cursorId.isZero())) {
450
445
  cursor._endSession();
451
446
  }
452
447
 
453
448
  if (
454
- cursor.cursorState.documents.length === 0 &&
455
- cursor.cursorState.cursorId &&
456
- cursor.cursorState.cursorId.isZero() &&
449
+ cursorState.documents.length === 0 &&
450
+ cursorState.cursorId &&
451
+ cursorState.cursorId.isZero() &&
457
452
  !cursor.cmd.tailable &&
458
453
  !cursor.cmd.awaitData
459
454
  ) {
@@ -699,8 +694,8 @@ function _setCursorNotifiedImpl(self, callback) {
699
694
  self.cursorState.documents = [];
700
695
  self.cursorState.cursorIndex = 0;
701
696
 
702
- if (self._endSession) {
703
- self._endSession(undefined, () => callback());
697
+ if (self.cursorState.session) {
698
+ self._endSession(callback);
704
699
  return;
705
700
  }
706
701
 
@@ -783,17 +778,9 @@ function nextFunction(self, callback) {
783
778
  // Execute the next get more
784
779
  self._getMore(function(err, doc, connection) {
785
780
  if (err) {
786
- if (err instanceof MongoError) {
787
- err[mongoErrorContextSymbol].isGetMore = true;
788
- }
789
-
790
781
  return handleCallback(callback, err);
791
782
  }
792
783
 
793
- if (self.cursorState.cursorId && self.cursorState.cursorId.isZero() && self._endSession) {
794
- self._endSession();
795
- }
796
-
797
784
  // Save the returned connection to ensure all getMore's fire over the same connection
798
785
  self.connection = connection;
799
786
 
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
  };
@@ -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
  }
@@ -463,11 +467,13 @@ function markServerUnknown(server, error) {
463
467
  }
464
468
 
465
469
  function makeOperationHandler(server, options, callback) {
470
+ const session = options && options.session;
471
+
466
472
  return function handleOperationResult(err, result) {
467
473
  if (err) {
468
474
  if (err instanceof MongoNetworkError) {
469
- if (options && options.session) {
470
- options.session.serverSession.isDirty = true;
475
+ if (session && !session.hasEnded) {
476
+ session.serverSession.isDirty = true;
471
477
  }
472
478
 
473
479
  if (!isNetworkTimeoutError(err)) {
@@ -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);
@@ -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 =
@@ -186,6 +192,10 @@ function readPreferenceServerSelector(readPreference) {
186
192
  );
187
193
  }
188
194
 
195
+ if (topologyDescription.type === TopologyType.Unknown) {
196
+ return [];
197
+ }
198
+
189
199
  if (
190
200
  topologyDescription.type === TopologyType.Single ||
191
201
  topologyDescription.type === TopologyType.Sharded
@@ -193,50 +203,32 @@ function readPreferenceServerSelector(readPreference) {
193
203
  return latencyWindowReducer(topologyDescription, servers.filter(knownFilter));
194
204
  }
195
205
 
196
- if (readPreference.mode === ReadPreference.PRIMARY) {
206
+ const mode = readPreference.mode;
207
+ if (mode === ReadPreference.PRIMARY) {
197
208
  return servers.filter(primaryFilter);
198
209
  }
199
210
 
200
- if (readPreference.mode === ReadPreference.SECONDARY) {
201
- return latencyWindowReducer(
202
- topologyDescription,
203
- tagSetReducer(
204
- readPreference,
205
- maxStalenessReducer(readPreference, topologyDescription, servers)
206
- )
207
- ).filter(secondaryFilter);
208
- } else if (readPreference.mode === ReadPreference.NEAREST) {
209
- return latencyWindowReducer(
210
- topologyDescription,
211
- tagSetReducer(
212
- readPreference,
213
- maxStalenessReducer(readPreference, topologyDescription, servers)
214
- )
215
- ).filter(nearestFilter);
216
- } else if (readPreference.mode === ReadPreference.SECONDARY_PREFERRED) {
217
- const result = latencyWindowReducer(
218
- topologyDescription,
219
- tagSetReducer(
220
- readPreference,
221
- maxStalenessReducer(readPreference, topologyDescription, servers)
222
- )
223
- ).filter(secondaryFilter);
224
-
225
- return result.length === 0 ? servers.filter(primaryFilter) : result;
226
- } else if (readPreference.mode === ReadPreference.PRIMARY_PREFERRED) {
211
+ if (mode === ReadPreference.PRIMARY_PREFERRED) {
227
212
  const result = servers.filter(primaryFilter);
228
213
  if (result.length) {
229
214
  return result;
230
215
  }
216
+ }
217
+
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
+ );
231
226
 
232
- return latencyWindowReducer(
233
- topologyDescription,
234
- tagSetReducer(
235
- readPreference,
236
- maxStalenessReducer(readPreference, topologyDescription, servers)
237
- )
238
- ).filter(secondaryFilter);
227
+ if (mode === ReadPreference.SECONDARY_PREFERRED && selectedServers.length === 0) {
228
+ return servers.filter(primaryFilter);
239
229
  }
230
+
231
+ return selectedServers;
240
232
  };
241
233
  }
242
234