mongodb 3.5.1 → 3.5.2

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,17 @@
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.2"></a>
6
+ ## [3.5.2](https://github.com/mongodb/node-mongodb-native/compare/v3.5.1...v3.5.2) (2020-01-20)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * properly handle err messages in MongoDB 2.6 servers ([0f4ab38](https://github.com/mongodb/node-mongodb-native/commit/0f4ab38))
12
+ * **topology:** always emit SDAM unrecoverable errors ([57f158f](https://github.com/mongodb/node-mongodb-native/commit/57f158f))
13
+
14
+
15
+
5
16
  <a name="3.5.1"></a>
6
17
  ## [3.5.1](https://github.com/mongodb/node-mongodb-native/compare/v3.5.0...v3.5.1) (2020-01-17)
7
18
 
@@ -243,14 +243,16 @@ function messageHandler(conn) {
243
243
  conn.emit('clusterTimeReceived', document.$clusterTime);
244
244
  }
245
245
 
246
- if (document.writeConcernError) {
247
- callback(new MongoWriteConcernError(document.writeConcernError, document));
248
- return;
249
- }
246
+ if (operationDescription.command) {
247
+ if (document.writeConcernError) {
248
+ callback(new MongoWriteConcernError(document.writeConcernError, document));
249
+ return;
250
+ }
250
251
 
251
- if (document.ok === 0 || document.$err || document.errmsg) {
252
- callback(new MongoError(document));
253
- return;
252
+ if (document.ok === 0 || document.$err || document.errmsg || document.code) {
253
+ callback(new MongoError(document));
254
+ return;
255
+ }
254
256
  }
255
257
  }
256
258
 
@@ -290,6 +292,7 @@ function write(command, options, callback) {
290
292
  fullResult: typeof options.fullResult === 'boolean' ? options.fullResult : false,
291
293
  noResponse: typeof options.noResponse === 'boolean' ? options.noResponse : false,
292
294
  documentsReturnedIn: options.documentsReturnedIn,
295
+ command: !!options.command,
293
296
 
294
297
  // for BSON parsing
295
298
  promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
package/lib/core/error.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const mongoErrorContextSymbol = Symbol('mongoErrorContextSymbol');
4
- const maxWireVersion = require('./utils').maxWireVersion;
5
4
 
6
5
  /**
7
6
  * Creates a new MongoError
@@ -237,9 +236,8 @@ function isNodeShuttingDownError(err) {
237
236
  * @ignore
238
237
  * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
239
238
  * @param {MongoError|Error} error
240
- * @param {Server} server
241
239
  */
242
- function isSDAMUnrecoverableError(error, server) {
240
+ function isSDAMUnrecoverableError(error) {
243
241
  // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
244
242
  // close event are considered unrecoverable
245
243
  if (error instanceof MongoParseError || error == null) {
@@ -247,10 +245,6 @@ function isSDAMUnrecoverableError(error, server) {
247
245
  }
248
246
 
249
247
  if (isRecoveringError(error) || isNotMasterError(error)) {
250
- if (maxWireVersion(server) >= 8 && !isNodeShuttingDownError(error)) {
251
- return false;
252
- }
253
-
254
248
  return true;
255
249
  }
256
250
 
@@ -266,5 +260,6 @@ module.exports = {
266
260
  MongoWriteConcernError,
267
261
  mongoErrorContextSymbol,
268
262
  isRetryableError,
269
- isSDAMUnrecoverableError
263
+ isSDAMUnrecoverableError,
264
+ isNodeShuttingDownError
270
265
  };
@@ -286,7 +286,7 @@ class Server extends EventEmitter {
286
286
  options.session.serverSession.isDirty = true;
287
287
  }
288
288
 
289
- if (isSDAMUnrecoverableError(err, this)) {
289
+ if (isSDAMUnrecoverableError(err)) {
290
290
  this.emit('error', err);
291
291
  }
292
292
  }
@@ -319,7 +319,7 @@ class Server extends EventEmitter {
319
319
  options.session.serverSession.isDirty = true;
320
320
  }
321
321
 
322
- if (isSDAMUnrecoverableError(err, this)) {
322
+ if (isSDAMUnrecoverableError(err)) {
323
323
  this.emit('error', err);
324
324
  }
325
325
  }
@@ -352,7 +352,7 @@ class Server extends EventEmitter {
352
352
  options.session.serverSession.isDirty = true;
353
353
  }
354
354
 
355
- if (isSDAMUnrecoverableError(err, this)) {
355
+ if (isSDAMUnrecoverableError(err)) {
356
356
  this.emit('error', err);
357
357
  }
358
358
  }
@@ -382,7 +382,7 @@ class Server extends EventEmitter {
382
382
  if (err) return cb(err);
383
383
 
384
384
  conn.killCursors(ns, cursorState, (err, result) => {
385
- if (err && isSDAMUnrecoverableError(err, this)) {
385
+ if (err && isSDAMUnrecoverableError(err)) {
386
386
  this.emit('error', err);
387
387
  }
388
388
 
@@ -489,7 +489,7 @@ function executeWriteOperation(args, options, callback) {
489
489
  options.session.serverSession.isDirty = true;
490
490
  }
491
491
 
492
- if (isSDAMUnrecoverableError(err, server)) {
492
+ if (isSDAMUnrecoverableError(err)) {
493
493
  server.emit('error', err);
494
494
  }
495
495
  }
@@ -3,12 +3,6 @@ const ServerType = require('./common').ServerType;
3
3
  const TopologyType = require('./common').TopologyType;
4
4
  const ReadPreference = require('../topologies/read_preference');
5
5
  const MongoError = require('../error').MongoError;
6
- const calculateDurationInMs = require('../utils').calculateDurationInMs;
7
- const MongoServerSelectionError = require('../error').MongoServerSelectionError;
8
-
9
- const common = require('./common');
10
- const STATE_CLOSED = common.STATE_CLOSED;
11
- const clearAndRemoveTimerFrom = common.clearAndRemoveTimerFrom;
12
6
 
13
7
  // max staleness constants
14
8
  const IDLE_WRITE_PERIOD = 10000;
@@ -246,90 +240,7 @@ function readPreferenceServerSelector(readPreference) {
246
240
  };
247
241
  }
248
242
 
249
- /**
250
- * Selects servers using the provided selector
251
- *
252
- * @private
253
- * @param {Topology} topology The topology to select servers from
254
- * @param {function} selector The predicate used for selecting servers
255
- * @param {Number} timeout The max time we are willing wait for selection
256
- * @param {Number} start A high precision timestamp for the start of the selection process
257
- * @param {function} callback The callback used to convey errors or the resultant servers
258
- */
259
- function selectServers(topology, selector, timeout, start, callback) {
260
- const duration = calculateDurationInMs(start);
261
- if (duration >= timeout) {
262
- return callback(
263
- new MongoServerSelectionError(`Server selection timed out after ${timeout} ms`),
264
- topology.description
265
- );
266
- }
267
-
268
- // explicitly disallow selection if client is closed
269
- if (topology.s.state === STATE_CLOSED) {
270
- callback(new MongoError('Topology is closed, please connect'));
271
- return;
272
- }
273
-
274
- // otherwise, attempt server selection
275
- const serverDescriptions = Array.from(topology.description.servers.values());
276
- let descriptions;
277
-
278
- // support server selection by options with readPreference
279
- if (typeof selector === 'object') {
280
- const readPreference = selector.readPreference
281
- ? selector.readPreference
282
- : ReadPreference.primary;
283
-
284
- selector = readPreferenceServerSelector(readPreference);
285
- }
286
-
287
- try {
288
- descriptions = selector
289
- ? selector(topology.description, serverDescriptions)
290
- : serverDescriptions;
291
- } catch (e) {
292
- return callback(e, null);
293
- }
294
-
295
- if (descriptions.length) {
296
- const servers = descriptions.map(description => topology.s.servers.get(description.address));
297
- return callback(null, servers);
298
- }
299
-
300
- const retrySelection = () => {
301
- // ensure all server monitors attempt monitoring soon
302
- topology.s.servers.forEach(server => process.nextTick(() => server.requestCheck()));
303
-
304
- const iterationTimer = setTimeout(() => {
305
- topology.removeListener('topologyDescriptionChanged', descriptionChangedHandler);
306
- callback(
307
- new MongoServerSelectionError(
308
- `Server selection timed out after ${timeout} ms`,
309
- topology.description
310
- )
311
- );
312
- }, timeout - duration);
313
-
314
- const descriptionChangedHandler = () => {
315
- // successful iteration, clear the check timer
316
- clearAndRemoveTimerFrom(iterationTimer, topology.s.iterationTimers);
317
-
318
- // topology description has changed due to monitoring, reattempt server selection
319
- selectServers(topology, selector, timeout, start, callback);
320
- };
321
-
322
- // track this timer in case we need to clean it up outside this loop
323
- topology.s.iterationTimers.add(iterationTimer);
324
-
325
- topology.once('topologyDescriptionChanged', descriptionChangedHandler);
326
- };
327
-
328
- retrySelection();
329
- }
330
-
331
243
  module.exports = {
332
- selectServers,
333
244
  writableServerSelector,
334
245
  readPreferenceServerSelector
335
246
  };
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const Denque = require('denque');
2
3
  const EventEmitter = require('events');
3
4
  const ServerDescription = require('./server_description').ServerDescription;
4
5
  const ServerType = require('./common').ServerType;
@@ -14,9 +15,11 @@ const deprecate = require('util').deprecate;
14
15
  const BSON = require('../connection/utils').retrieveBSON();
15
16
  const createCompressionInfo = require('../topologies/shared').createCompressionInfo;
16
17
  const isRetryableError = require('../error').isRetryableError;
17
- const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
18
+ const isNodeShuttingDownError = require('../error').isNodeShuttingDownError;
19
+ const maxWireVersion = require('../utils').maxWireVersion;
18
20
  const ClientSession = require('../sessions').ClientSession;
19
21
  const MongoError = require('../error').MongoError;
22
+ const MongoServerSelectionError = require('../error').MongoServerSelectionError;
20
23
  const resolveClusterTime = require('../topologies/shared').resolveClusterTime;
21
24
  const SrvPoller = require('./srv_polling').SrvPoller;
22
25
  const getMMAPError = require('../topologies/shared').getMMAPError;
@@ -34,7 +37,7 @@ const clearAndRemoveTimerFrom = common.clearAndRemoveTimerFrom;
34
37
  const serverSelection = require('./server_selection');
35
38
  const readPreferenceServerSelector = serverSelection.readPreferenceServerSelector;
36
39
  const writableServerSelector = serverSelection.writableServerSelector;
37
- const selectServers = serverSelection.selectServers;
40
+ // const selectServers = serverSelection.selectServers;
38
41
 
39
42
  // Global state
40
43
  let globalTopologyCounter = 0;
@@ -73,6 +76,9 @@ const DEPRECATED_OPTIONS = new Set([
73
76
  'bufferMaxEntries'
74
77
  ]);
75
78
 
79
+ const kCancelled = Symbol('cancelled');
80
+ const kWaitQueue = Symbol('waitQueue');
81
+
76
82
  /**
77
83
  * A container of server instances representing a connection to a MongoDB topology.
78
84
  *
@@ -139,6 +145,7 @@ class Topology extends EventEmitter {
139
145
  return result;
140
146
  }, new Map());
141
147
 
148
+ this[kWaitQueue] = new Denque();
142
149
  this.s = {
143
150
  // the id of this topology
144
151
  id: topologyId,
@@ -194,7 +201,6 @@ class Topology extends EventEmitter {
194
201
  clusterTime: null,
195
202
 
196
203
  // timer management
197
- iterationTimers: new Set(),
198
204
  connectionTimers: new Set()
199
205
  };
200
206
 
@@ -334,8 +340,7 @@ class Topology extends EventEmitter {
334
340
  return;
335
341
  }
336
342
 
337
- // clear all existing monitor timers
338
- drainTimerQueue(this.s.iterationTimers);
343
+ drainWaitQueue(this[kWaitQueue], new MongoError('Topology closed'));
339
344
  drainTimerQueue(this.s.connectionTimers);
340
345
 
341
346
  if (this.s.srvPoller) {
@@ -415,26 +420,43 @@ class Topology extends EventEmitter {
415
420
  const transaction = session && session.transaction;
416
421
 
417
422
  if (isSharded && transaction && transaction.server) {
418
- callback(null, transaction.server);
423
+ callback(undefined, transaction.server);
419
424
  return;
420
425
  }
421
426
 
422
- selectServers(
423
- this,
424
- selector,
425
- options.serverSelectionTimeoutMS,
426
- process.hrtime(),
427
- (err, servers) => {
428
- if (err) return callback(err);
429
-
430
- const selectedServer = randomSelection(servers);
431
- if (isSharded && transaction && transaction.isActive) {
432
- transaction.pinServer(selectedServer);
433
- }
427
+ // support server selection by options with readPreference
428
+ let serverSelector = selector;
429
+ if (typeof selector === 'object') {
430
+ const readPreference = selector.readPreference
431
+ ? selector.readPreference
432
+ : ReadPreference.primary;
434
433
 
435
- callback(null, selectedServer);
436
- }
437
- );
434
+ serverSelector = readPreferenceServerSelector(readPreference);
435
+ }
436
+
437
+ const waitQueueMember = {
438
+ serverSelector,
439
+ transaction,
440
+ callback
441
+ };
442
+
443
+ const serverSelectionTimeoutMS = options.serverSelectionTimeoutMS;
444
+ if (serverSelectionTimeoutMS) {
445
+ waitQueueMember.timer = setTimeout(() => {
446
+ waitQueueMember[kCancelled] = true;
447
+ waitQueueMember.timer = undefined;
448
+ const timeoutError = new MongoServerSelectionError(
449
+ `Server selection timed out after ${serverSelectionTimeoutMS} ms`,
450
+ this.description
451
+ );
452
+
453
+ waitQueueMember.callback(timeoutError);
454
+ }, serverSelectionTimeoutMS);
455
+ }
456
+
457
+ // place the member at the front of the wait queue
458
+ this[kWaitQueue].unshift(waitQueueMember);
459
+ processWaitQueue(this);
438
460
  }
439
461
 
440
462
  // Sessions related methods
@@ -544,6 +566,11 @@ class Topology extends EventEmitter {
544
566
  // update server list from updated descriptions
545
567
  updateServers(this, serverDescription);
546
568
 
569
+ // attempt to resolve any outstanding server selection attempts
570
+ if (this[kWaitQueue].length > 0) {
571
+ processWaitQueue(this);
572
+ }
573
+
547
574
  this.emit(
548
575
  'topologyDescriptionChanged',
549
576
  new events.TopologyDescriptionChangedEvent(
@@ -881,14 +908,12 @@ function serverErrorEventHandler(server, topology) {
881
908
  return;
882
909
  }
883
910
 
884
- if (isSDAMUnrecoverableError(err, server)) {
885
- // NOTE: this must be commented out until we switch to the new CMAP pool because
886
- // we presently _always_ clear the pool on error.
887
- resetServerState(server, err, { clearPool: true });
911
+ if (maxWireVersion(server) >= 8 && !isNodeShuttingDownError(err)) {
912
+ resetServerState(server, err);
888
913
  return;
889
914
  }
890
915
 
891
- resetServerState(server, err);
916
+ resetServerState(server, err, { clearPool: true });
892
917
  };
893
918
  }
894
919
 
@@ -1013,6 +1038,64 @@ function srvPollingHandler(topology) {
1013
1038
  };
1014
1039
  }
1015
1040
 
1041
+ function drainWaitQueue(queue, err) {
1042
+ while (queue.length) {
1043
+ const waitQueueMember = queue.pop();
1044
+ clearTimeout(waitQueueMember.timer);
1045
+ if (!waitQueueMember[kCancelled]) {
1046
+ waitQueueMember.callback(err);
1047
+ }
1048
+ }
1049
+ }
1050
+
1051
+ function processWaitQueue(topology) {
1052
+ if (topology.s.state === STATE_CLOSED) {
1053
+ drainWaitQueue(topology[kWaitQueue], new MongoError('Topology is closed, please connect'));
1054
+ return;
1055
+ }
1056
+
1057
+ const isSharded = topology.description.type === TopologyType.Sharded;
1058
+ const serverDescriptions = Array.from(topology.description.servers.values());
1059
+ for (let i = 0; i < topology[kWaitQueue].length; ++i) {
1060
+ const waitQueueMember = topology[kWaitQueue].shift();
1061
+ if (waitQueueMember[kCancelled]) {
1062
+ continue;
1063
+ }
1064
+
1065
+ let selectedDescriptions;
1066
+ try {
1067
+ const serverSelector = waitQueueMember.serverSelector;
1068
+ selectedDescriptions = serverSelector
1069
+ ? serverSelector(topology.description, serverDescriptions)
1070
+ : serverDescriptions;
1071
+ } catch (e) {
1072
+ clearTimeout(waitQueueMember.timer);
1073
+ waitQueueMember.callback(e);
1074
+ break;
1075
+ }
1076
+
1077
+ if (selectedDescriptions.length === 0) {
1078
+ topology[kWaitQueue].push(waitQueueMember);
1079
+ break;
1080
+ }
1081
+
1082
+ const selectedServerDescription = randomSelection(selectedDescriptions);
1083
+ const selectedServer = topology.s.servers.get(selectedServerDescription.address);
1084
+ const transaction = waitQueueMember.transaction;
1085
+ if (isSharded && transaction && transaction.isActive) {
1086
+ transaction.pinServer(selectedServer);
1087
+ }
1088
+
1089
+ clearTimeout(waitQueueMember.timer);
1090
+ waitQueueMember.callback(undefined, selectedServer);
1091
+ }
1092
+
1093
+ if (topology[kWaitQueue].length > 0) {
1094
+ // ensure all server monitors attempt monitoring soon
1095
+ topology.s.servers.forEach(server => process.nextTick(() => server.requestCheck()));
1096
+ }
1097
+ }
1098
+
1016
1099
  /**
1017
1100
  * A server opening SDAM monitoring event
1018
1101
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mongodb",
3
- "version": "3.5.1",
3
+ "version": "3.5.2",
4
4
  "description": "The official MongoDB driver for Node.js",
5
5
  "main": "index.js",
6
6
  "files": [