mongodb 3.4.0 → 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.
Files changed (45) hide show
  1. package/HISTORY.md +72 -0
  2. package/index.js +1 -0
  3. package/lib/bulk/common.js +1 -1
  4. package/lib/cmap/connection.js +369 -0
  5. package/lib/cmap/connection_pool.js +593 -0
  6. package/lib/cmap/errors.js +35 -0
  7. package/lib/cmap/events.js +154 -0
  8. package/lib/{core/cmap → cmap}/message_stream.js +19 -17
  9. package/lib/cmap/stream_description.js +45 -0
  10. package/lib/core/auth/scram.js +1 -1
  11. package/lib/core/connection/apm.js +24 -7
  12. package/lib/core/connection/connect.js +55 -26
  13. package/lib/core/connection/pool.js +3 -16
  14. package/lib/core/error.js +27 -10
  15. package/lib/core/index.js +1 -0
  16. package/lib/core/sdam/events.js +124 -0
  17. package/lib/core/sdam/monitor.js +251 -0
  18. package/lib/core/sdam/server.js +148 -198
  19. package/lib/core/sdam/server_description.js +6 -4
  20. package/lib/core/sdam/server_selection.js +0 -91
  21. package/lib/core/sdam/topology.js +162 -136
  22. package/lib/core/sdam/topology_description.js +10 -8
  23. package/lib/core/sessions.js +16 -3
  24. package/lib/core/topologies/mongos.js +5 -13
  25. package/lib/core/topologies/replset.js +5 -10
  26. package/lib/core/topologies/server.js +9 -4
  27. package/lib/core/topologies/shared.js +0 -60
  28. package/lib/core/transactions.js +18 -7
  29. package/lib/core/uri_parser.js +3 -0
  30. package/lib/core/utils.js +84 -18
  31. package/lib/core/wireprotocol/command.js +2 -9
  32. package/lib/gridfs-stream/upload.js +1 -1
  33. package/lib/mongo_client.js +6 -6
  34. package/lib/operations/connect.js +118 -49
  35. package/lib/operations/execute_operation.js +31 -40
  36. package/lib/operations/find_one.js +13 -9
  37. package/lib/topologies/mongos.js +2 -9
  38. package/lib/topologies/native_topology.js +2 -6
  39. package/lib/topologies/replset.js +2 -9
  40. package/lib/topologies/server.js +2 -9
  41. package/lib/topologies/topology_base.js +4 -25
  42. package/lib/utils.js +11 -2
  43. package/package.json +3 -2
  44. package/lib/core/cmap/connection.js +0 -220
  45. package/lib/core/sdam/monitoring.js +0 -241
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The base class for all monitoring events published from the connection pool
5
+ *
6
+ * @property {number} time A timestamp when the event was created
7
+ * @property {string} address The address (host/port pair) of the pool
8
+ */
9
+ class ConnectionPoolMonitoringEvent {
10
+ constructor(pool) {
11
+ this.time = new Date();
12
+ this.address = pool.address;
13
+ }
14
+ }
15
+
16
+ /**
17
+ * An event published when a connection pool is created
18
+ *
19
+ * @property {Object} options The options used to create this connection pool
20
+ */
21
+ class ConnectionPoolCreatedEvent extends ConnectionPoolMonitoringEvent {
22
+ constructor(pool) {
23
+ super(pool);
24
+ this.options = pool.options;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * An event published when a connection pool is closed
30
+ */
31
+ class ConnectionPoolClosedEvent extends ConnectionPoolMonitoringEvent {
32
+ constructor(pool) {
33
+ super(pool);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * An event published when a connection pool creates a new connection
39
+ *
40
+ * @property {number} connectionId A monotonically increasing, per-pool id for the newly created connection
41
+ */
42
+ class ConnectionCreatedEvent extends ConnectionPoolMonitoringEvent {
43
+ constructor(pool, connection) {
44
+ super(pool);
45
+ this.connectionId = connection.id;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * An event published when a connection is ready for use
51
+ *
52
+ * @property {number} connectionId The id of the connection
53
+ */
54
+ class ConnectionReadyEvent extends ConnectionPoolMonitoringEvent {
55
+ constructor(pool, connection) {
56
+ super(pool);
57
+ this.connectionId = connection.id;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * An event published when a connection is closed
63
+ *
64
+ * @property {number} connectionId The id of the connection
65
+ * @property {string} reason The reason the connection was closed
66
+ */
67
+ class ConnectionClosedEvent extends ConnectionPoolMonitoringEvent {
68
+ constructor(pool, connection, reason) {
69
+ super(pool);
70
+ this.connectionId = connection.id;
71
+ this.reason = reason || 'unknown';
72
+ }
73
+ }
74
+
75
+ /**
76
+ * An event published when a request to check a connection out begins
77
+ */
78
+ class ConnectionCheckOutStartedEvent extends ConnectionPoolMonitoringEvent {
79
+ constructor(pool) {
80
+ super(pool);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * An event published when a request to check a connection out fails
86
+ *
87
+ * @property {string} reason The reason the attempt to check out failed
88
+ */
89
+ class ConnectionCheckOutFailedEvent extends ConnectionPoolMonitoringEvent {
90
+ constructor(pool, reason) {
91
+ super(pool);
92
+ this.reason = reason;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * An event published when a connection is checked out of the connection pool
98
+ *
99
+ * @property {number} connectionId The id of the connection
100
+ */
101
+ class ConnectionCheckedOutEvent extends ConnectionPoolMonitoringEvent {
102
+ constructor(pool, connection) {
103
+ super(pool);
104
+ this.connectionId = connection.id;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * An event published when a connection is checked into the connection pool
110
+ *
111
+ * @property {number} connectionId The id of the connection
112
+ */
113
+ class ConnectionCheckedInEvent extends ConnectionPoolMonitoringEvent {
114
+ constructor(pool, connection) {
115
+ super(pool);
116
+ this.connectionId = connection.id;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * An event published when a connection pool is cleared
122
+ */
123
+ class ConnectionPoolClearedEvent extends ConnectionPoolMonitoringEvent {
124
+ constructor(pool) {
125
+ super(pool);
126
+ }
127
+ }
128
+
129
+ const CMAP_EVENT_NAMES = [
130
+ 'connectionPoolCreated',
131
+ 'connectionPoolClosed',
132
+ 'connectionCreated',
133
+ 'connectionReady',
134
+ 'connectionClosed',
135
+ 'connectionCheckOutStarted',
136
+ 'connectionCheckOutFailed',
137
+ 'connectionCheckedOut',
138
+ 'connectionCheckedIn',
139
+ 'connectionPoolCleared'
140
+ ];
141
+
142
+ module.exports = {
143
+ CMAP_EVENT_NAMES,
144
+ ConnectionPoolCreatedEvent,
145
+ ConnectionPoolClosedEvent,
146
+ ConnectionCreatedEvent,
147
+ ConnectionReadyEvent,
148
+ ConnectionClosedEvent,
149
+ ConnectionCheckOutStartedEvent,
150
+ ConnectionCheckOutFailedEvent,
151
+ ConnectionCheckedOutEvent,
152
+ ConnectionCheckedInEvent,
153
+ ConnectionPoolClearedEvent
154
+ };
@@ -2,20 +2,20 @@
2
2
 
3
3
  const Duplex = require('stream').Duplex;
4
4
  const BufferList = require('bl');
5
- const MongoParseError = require('../error').MongoParseError;
6
- const decompress = require('../wireprotocol/compression').decompress;
7
- const Response = require('../connection/commands').Response;
8
- const BinMsg = require('../connection/msg').BinMsg;
9
- const MongoError = require('../error').MongoError;
10
- const OP_COMPRESSED = require('../wireprotocol/shared').opcodes.OP_COMPRESSED;
11
- const OP_MSG = require('../wireprotocol/shared').opcodes.OP_MSG;
12
- const MESSAGE_HEADER_SIZE = require('../wireprotocol/shared').MESSAGE_HEADER_SIZE;
13
- const COMPRESSION_DETAILS_SIZE = require('../wireprotocol/shared').COMPRESSION_DETAILS_SIZE;
14
- const opcodes = require('../wireprotocol/shared').opcodes;
15
- const compress = require('../wireprotocol/compression').compress;
16
- const compressorIDs = require('../wireprotocol/compression').compressorIDs;
17
- const uncompressibleCommands = require('../wireprotocol/compression').uncompressibleCommands;
18
- const Msg = require('../connection/msg').Msg;
5
+ const MongoParseError = require('../core/error').MongoParseError;
6
+ const decompress = require('../core/wireprotocol/compression').decompress;
7
+ const Response = require('../core/connection/commands').Response;
8
+ const BinMsg = require('../core/connection/msg').BinMsg;
9
+ const MongoError = require('../core/error').MongoError;
10
+ const OP_COMPRESSED = require('../core/wireprotocol/shared').opcodes.OP_COMPRESSED;
11
+ const OP_MSG = require('../core/wireprotocol/shared').opcodes.OP_MSG;
12
+ const MESSAGE_HEADER_SIZE = require('../core/wireprotocol/shared').MESSAGE_HEADER_SIZE;
13
+ const COMPRESSION_DETAILS_SIZE = require('../core/wireprotocol/shared').COMPRESSION_DETAILS_SIZE;
14
+ const opcodes = require('../core/wireprotocol/shared').opcodes;
15
+ const compress = require('../core/wireprotocol/compression').compress;
16
+ const compressorIDs = require('../core/wireprotocol/compression').compressorIDs;
17
+ const uncompressibleCommands = require('../core/wireprotocol/compression').uncompressibleCommands;
18
+ const Msg = require('../core/connection/msg').Msg;
19
19
 
20
20
  const kDefaultMaxBsonMessageSize = 1024 * 1024 * 16 * 4;
21
21
  const kBuffer = Symbol('buffer');
@@ -61,8 +61,9 @@ class MessageStream extends Duplex {
61
61
  }
62
62
 
63
63
  const messageBuffer = buffer.slice(0, sizeOfMessage);
64
- processMessage(this, messageBuffer, callback);
65
64
  buffer.consume(sizeOfMessage);
65
+
66
+ processMessage(this, messageBuffer, callback);
66
67
  }
67
68
  }
68
69
 
@@ -76,7 +77,8 @@ class MessageStream extends Duplex {
76
77
  // TODO: agreed compressor should live in `StreamDescription`
77
78
  const shouldCompress = operationDescription && !!operationDescription.agreedCompressor;
78
79
  if (!shouldCompress || !canCompress(command)) {
79
- this.push(Buffer.concat(command.toBin()));
80
+ const data = command.toBin();
81
+ this.push(Array.isArray(data) ? Buffer.concat(data) : data);
80
82
  return;
81
83
  }
82
84
 
@@ -125,7 +127,7 @@ function canCompress(command) {
125
127
 
126
128
  function processMessage(stream, message, callback) {
127
129
  const messageHeader = {
128
- messageLength: message.readInt32LE(0),
130
+ length: message.readInt32LE(0),
129
131
  requestId: message.readInt32LE(4),
130
132
  responseTo: message.readInt32LE(8),
131
133
  opCode: message.readInt32LE(12)
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+ const parseServerType = require('../core/sdam/server_description').parseServerType;
3
+
4
+ const RESPONSE_FIELDS = [
5
+ 'minWireVersion',
6
+ 'maxWireVersion',
7
+ 'maxBsonObjectSize',
8
+ 'maxMessageSizeBytes',
9
+ 'maxWriteBatchSize',
10
+ '__nodejs_mock_server__'
11
+ ];
12
+
13
+ class StreamDescription {
14
+ constructor(address, options) {
15
+ this.address = address;
16
+ this.type = parseServerType(null);
17
+ this.minWireVersion = undefined;
18
+ this.maxWireVersion = undefined;
19
+ this.maxBsonObjectSize = 16777216;
20
+ this.maxMessageSizeBytes = 48000000;
21
+ this.maxWriteBatchSize = 100000;
22
+ this.compressors =
23
+ options && options.compression && Array.isArray(options.compression.compressors)
24
+ ? options.compression.compressors
25
+ : [];
26
+ }
27
+
28
+ receiveResponse(response) {
29
+ this.type = parseServerType(response);
30
+
31
+ RESPONSE_FIELDS.forEach(field => {
32
+ if (typeof response[field] !== 'undefined') {
33
+ this[field] = response[field];
34
+ }
35
+ });
36
+
37
+ if (response.compression) {
38
+ this.compressor = this.compressors.filter(c => response.compression.indexOf(c) !== -1)[0];
39
+ }
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ StreamDescription
45
+ };
@@ -236,7 +236,7 @@ class ScramSHA extends AuthProvider {
236
236
  };
237
237
 
238
238
  sendAuthCommand(connection, `${db}.$cmd`, saslContinueCmd, (err, r) => {
239
- if (r && typeof r.ok === 'number' && r.ok === 0) {
239
+ if (err || (r && typeof r.ok === 'number' && r.ok === 0)) {
240
240
  callback(err, r);
241
241
  return;
242
242
  }
@@ -23,8 +23,9 @@ const namespace = command => command.ns;
23
23
  const databaseName = command => command.ns.split('.')[0];
24
24
  const collectionName = command => command.ns.split('.')[1];
25
25
  const generateConnectionId = pool =>
26
- pool.options ? `${pool.options.host}:${pool.options.port}` : pool.id;
26
+ pool.options ? `${pool.options.host}:${pool.options.port}` : pool.address;
27
27
  const maybeRedact = (commandName, result) => (SENSITIVE_COMMANDS.has(commandName) ? {} : result);
28
+ const isLegacyPool = pool => pool.s && pool.queue;
28
29
 
29
30
  const LEGACY_FIND_QUERY_MAP = {
30
31
  $query: 'filter',
@@ -151,6 +152,22 @@ const extractReply = (command, reply) => {
151
152
  return reply && reply.result ? reply.result : reply;
152
153
  };
153
154
 
155
+ const extractConnectionDetails = pool => {
156
+ if (isLegacyPool(pool)) {
157
+ return {
158
+ connectionId: generateConnectionId(pool)
159
+ };
160
+ }
161
+
162
+ // APM in the modern pool is done at the `Connection` level, so we rename it here for
163
+ // readability.
164
+ const connection = pool;
165
+ return {
166
+ address: connection.address,
167
+ connectionId: connection.id
168
+ };
169
+ };
170
+
154
171
  /** An event indicating the start of a given command */
155
172
  class CommandStartedEvent {
156
173
  /**
@@ -162,6 +179,7 @@ class CommandStartedEvent {
162
179
  constructor(pool, command) {
163
180
  const cmd = extractCommand(command);
164
181
  const commandName = extractCommandName(cmd);
182
+ const connectionDetails = extractConnectionDetails(pool);
165
183
 
166
184
  // NOTE: remove in major revision, this is not spec behavior
167
185
  if (SENSITIVE_COMMANDS.has(commandName)) {
@@ -169,8 +187,7 @@ class CommandStartedEvent {
169
187
  this.commandObj[commandName] = true;
170
188
  }
171
189
 
172
- Object.assign(this, {
173
- connectionId: generateConnectionId(pool),
190
+ Object.assign(this, connectionDetails, {
174
191
  requestId: command.requestId,
175
192
  databaseName: databaseName(command),
176
193
  commandName,
@@ -192,9 +209,9 @@ class CommandSucceededEvent {
192
209
  constructor(pool, command, reply, started) {
193
210
  const cmd = extractCommand(command);
194
211
  const commandName = extractCommandName(cmd);
212
+ const connectionDetails = extractConnectionDetails(pool);
195
213
 
196
- Object.assign(this, {
197
- connectionId: generateConnectionId(pool),
214
+ Object.assign(this, connectionDetails, {
198
215
  requestId: command.requestId,
199
216
  commandName,
200
217
  duration: calculateDurationInMs(started),
@@ -216,9 +233,9 @@ class CommandFailedEvent {
216
233
  constructor(pool, command, error, started) {
217
234
  const cmd = extractCommand(command);
218
235
  const commandName = extractCommandName(cmd);
236
+ const connectionDetails = extractConnectionDetails(pool);
219
237
 
220
- Object.assign(this, {
221
- connectionId: generateConnectionId(pool),
238
+ Object.assign(this, connectionDetails, {
222
239
  requestId: command.requestId,
223
240
  commandName,
224
241
  duration: calculateDurationInMs(started),
@@ -3,11 +3,11 @@ const net = require('net');
3
3
  const tls = require('tls');
4
4
  const Connection = require('./connection');
5
5
  const Query = require('./commands').Query;
6
- const createClientInfo = require('../topologies/shared').createClientInfo;
7
6
  const MongoError = require('../error').MongoError;
8
7
  const MongoNetworkError = require('../error').MongoNetworkError;
9
8
  const defaultAuthProviders = require('../auth/defaultAuthProviders').defaultAuthProviders;
10
9
  const WIRE_CONSTANTS = require('../wireprotocol/constants');
10
+ const makeClientMetadata = require('../utils').makeClientMetadata;
11
11
  const MAX_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_WIRE_VERSION;
12
12
  const MAX_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_SERVER_VERSION;
13
13
  const MIN_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_WIRE_VERSION;
@@ -36,6 +36,10 @@ function connect(options, cancellationToken, callback) {
36
36
  });
37
37
  }
38
38
 
39
+ function isModernConnectionType(conn) {
40
+ return typeof conn.command === 'function';
41
+ }
42
+
39
43
  function getSaslSupportedMechs(options) {
40
44
  if (!(options && options.credentials)) {
41
45
  return {};
@@ -101,42 +105,51 @@ function performInitialHandshake(conn, options, _callback) {
101
105
  const handshakeDoc = Object.assign(
102
106
  {
103
107
  ismaster: true,
104
- client: createClientInfo(options),
108
+ client: options.metadata || makeClientMetadata(options),
105
109
  compression: compressors
106
110
  },
107
111
  getSaslSupportedMechs(options)
108
112
  );
109
113
 
114
+ const handshakeOptions = Object.assign({}, options);
115
+
116
+ // The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
117
+ if (options.connectTimeoutMS || options.connectionTimeout) {
118
+ handshakeOptions.socketTimeout = options.connectTimeoutMS || options.connectionTimeout;
119
+ }
120
+
110
121
  const start = new Date().getTime();
111
- runCommand(conn, 'admin.$cmd', handshakeDoc, options, (err, ismaster) => {
122
+ runCommand(conn, 'admin.$cmd', handshakeDoc, handshakeOptions, (err, ismaster) => {
112
123
  if (err) {
113
- callback(err, null);
124
+ callback(err);
114
125
  return;
115
126
  }
116
127
 
117
128
  if (ismaster.ok === 0) {
118
- callback(new MongoError(ismaster), null);
129
+ callback(new MongoError(ismaster));
119
130
  return;
120
131
  }
121
132
 
122
133
  const supportedServerErr = checkSupportedServer(ismaster, options);
123
134
  if (supportedServerErr) {
124
- callback(supportedServerErr, null);
135
+ callback(supportedServerErr);
125
136
  return;
126
137
  }
127
138
 
128
- // resolve compression
129
- if (ismaster.compression) {
130
- const agreedCompressors = compressors.filter(
131
- compressor => ismaster.compression.indexOf(compressor) !== -1
132
- );
139
+ if (!isModernConnectionType(conn)) {
140
+ // resolve compression
141
+ if (ismaster.compression) {
142
+ const agreedCompressors = compressors.filter(
143
+ compressor => ismaster.compression.indexOf(compressor) !== -1
144
+ );
133
145
 
134
- if (agreedCompressors.length) {
135
- conn.agreedCompressor = agreedCompressors[0];
136
- }
146
+ if (agreedCompressors.length) {
147
+ conn.agreedCompressor = agreedCompressors[0];
148
+ }
137
149
 
138
- if (options.compression && options.compression.zlibCompressionLevel) {
139
- conn.zlibCompressionLevel = options.compression.zlibCompressionLevel;
150
+ if (options.compression && options.compression.zlibCompressionLevel) {
151
+ conn.zlibCompressionLevel = options.compression.zlibCompressionLevel;
152
+ }
140
153
  }
141
154
  }
142
155
 
@@ -153,7 +166,7 @@ function performInitialHandshake(conn, options, _callback) {
153
166
  return;
154
167
  }
155
168
 
156
- callback(null, conn);
169
+ callback(undefined, conn);
157
170
  });
158
171
  }
159
172
 
@@ -229,7 +242,11 @@ function makeConnection(family, options, cancellationToken, _callback) {
229
242
  typeof options.keepAliveInitialDelay === 'number' ? options.keepAliveInitialDelay : 300000;
230
243
  const noDelay = typeof options.noDelay === 'boolean' ? options.noDelay : true;
231
244
  const connectionTimeout =
232
- typeof options.connectionTimeout === 'number' ? options.connectionTimeout : 30000;
245
+ typeof options.connectionTimeout === 'number'
246
+ ? options.connectionTimeout
247
+ : typeof options.connectTimeoutMS === 'number'
248
+ ? options.connectTimeoutMS
249
+ : 30000;
233
250
  const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
234
251
  const rejectUnauthorized =
235
252
  typeof options.rejectUnauthorized === 'boolean' ? options.rejectUnauthorized : true;
@@ -264,6 +281,7 @@ function makeConnection(family, options, cancellationToken, _callback) {
264
281
  socket.setTimeout(connectionTimeout);
265
282
  socket.setNoDelay(noDelay);
266
283
 
284
+ const connectEvent = useSsl ? 'secureConnect' : 'connect';
267
285
  let cancellationHandler;
268
286
  function errorHandler(eventName) {
269
287
  return err => {
@@ -272,7 +290,7 @@ function makeConnection(family, options, cancellationToken, _callback) {
272
290
  cancellationToken.removeListener('cancel', cancellationHandler);
273
291
  }
274
292
 
275
- socket.removeListener('connect', connectHandler);
293
+ socket.removeListener(connectEvent, connectHandler);
276
294
  callback(connectionFailureError(eventName, err));
277
295
  };
278
296
  }
@@ -297,17 +315,28 @@ function makeConnection(family, options, cancellationToken, _callback) {
297
315
  cancellationToken.once('cancel', cancellationHandler);
298
316
  }
299
317
 
300
- socket.once('connect', connectHandler);
318
+ socket.once(connectEvent, connectHandler);
301
319
  }
302
320
 
303
321
  const CONNECTION_ERROR_EVENTS = ['error', 'close', 'timeout', 'parseError'];
304
322
  function runCommand(conn, ns, command, options, callback) {
305
- if (typeof conn.command === 'function') {
306
- conn.command(ns, command, options, callback);
323
+ if (typeof options === 'function') (callback = options), (options = {});
324
+
325
+ // are we using the new connection type? if so, no need to simulate a rpc `command` method
326
+ if (isModernConnectionType(conn)) {
327
+ conn.command(ns, command, options, (err, result) => {
328
+ if (err) {
329
+ callback(err);
330
+ return;
331
+ }
332
+
333
+ // NODE-2382: raw wire protocol messages, or command results should not be used anymore
334
+ callback(undefined, result.result);
335
+ });
336
+
307
337
  return;
308
338
  }
309
339
 
310
- if (typeof options === 'function') (callback = options), (options = {});
311
340
  const socketTimeout = typeof options.socketTimeout === 'number' ? options.socketTimeout : 360000;
312
341
  const bson = conn.options.bson;
313
342
  const query = new Query(bson, ns, command, {
@@ -333,7 +362,7 @@ function runCommand(conn, ns, command, options, callback) {
333
362
  // ignore all future errors
334
363
  conn.on('error', noop);
335
364
 
336
- _callback(err, null);
365
+ _callback(err);
337
366
  }
338
367
 
339
368
  function messageHandler(msg) {
@@ -346,7 +375,7 @@ function runCommand(conn, ns, command, options, callback) {
346
375
  conn.removeListener('message', messageHandler);
347
376
 
348
377
  msg.parse({ promoteValues: true });
349
- _callback(null, msg.documents[0]);
378
+ _callback(undefined, msg.documents[0]);
350
379
  }
351
380
 
352
381
  conn.setSocketTimeout(socketTimeout);
@@ -365,7 +394,7 @@ function authenticate(conn, credentials, callback) {
365
394
  const provider = AUTH_PROVIDERS[mechanism];
366
395
  provider.auth(runCommand, [conn], credentials, err => {
367
396
  if (err) return callback(err);
368
- callback(null, conn);
397
+ callback(undefined, conn);
369
398
  });
370
399
  }
371
400
 
@@ -255,22 +255,6 @@ function connectionFailureHandler(pool, event, err, conn) {
255
255
  // Remove the connection
256
256
  removeConnection(pool, conn);
257
257
 
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;
272
- }
273
-
274
258
  // flush remaining work items
275
259
  conn.flush(err);
276
260
  }
@@ -641,6 +625,9 @@ function destroy(self, connections, options, callback) {
641
625
  conn.removeAllListeners(eventName);
642
626
  }
643
627
 
628
+ // ignore any errors during destruction
629
+ conn.on('error', () => {});
630
+
644
631
  conn.destroy(options, cb);
645
632
  },
646
633
  err => {
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
@@ -95,14 +94,35 @@ class MongoParseError extends MongoError {
95
94
  */
96
95
  class MongoTimeoutError extends MongoError {
97
96
  constructor(message, reason) {
98
- super(message);
97
+ if (reason && reason.error) {
98
+ super(reason.error.message || reason.error);
99
+ } else {
100
+ super(message);
101
+ }
102
+
99
103
  this.name = 'MongoTimeoutError';
100
- if (reason != null) {
104
+ if (reason) {
101
105
  this.reason = reason;
102
106
  }
103
107
  }
104
108
  }
105
109
 
110
+ /**
111
+ * An error signifying a client-side server selection error
112
+ *
113
+ * @param {Error|string|object} message The error message
114
+ * @param {string|object} [reason] The reason the timeout occured
115
+ * @property {string} message The error message
116
+ * @property {string} [reason] An optional reason context for the timeout, generally an error saved during flow of monitoring and selecting servers
117
+ * @extends MongoError
118
+ */
119
+ class MongoServerSelectionError extends MongoTimeoutError {
120
+ constructor(message, reason) {
121
+ super(message, reason);
122
+ this.name = 'MongoServerSelectionError';
123
+ }
124
+ }
125
+
106
126
  function makeWriteConcernResultObject(input) {
107
127
  const output = Object.assign({}, input);
108
128
 
@@ -216,9 +236,8 @@ function isNodeShuttingDownError(err) {
216
236
  * @ignore
217
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
218
238
  * @param {MongoError|Error} error
219
- * @param {Server} server
220
239
  */
221
- function isSDAMUnrecoverableError(error, server) {
240
+ function isSDAMUnrecoverableError(error) {
222
241
  // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
223
242
  // close event are considered unrecoverable
224
243
  if (error instanceof MongoParseError || error == null) {
@@ -226,10 +245,6 @@ function isSDAMUnrecoverableError(error, server) {
226
245
  }
227
246
 
228
247
  if (isRecoveringError(error) || isNotMasterError(error)) {
229
- if (maxWireVersion(server) >= 8 && !isNodeShuttingDownError(error)) {
230
- return false;
231
- }
232
-
233
248
  return true;
234
249
  }
235
250
 
@@ -241,8 +256,10 @@ module.exports = {
241
256
  MongoNetworkError,
242
257
  MongoParseError,
243
258
  MongoTimeoutError,
259
+ MongoServerSelectionError,
244
260
  MongoWriteConcernError,
245
261
  mongoErrorContextSymbol,
246
262
  isRetryableError,
247
- isSDAMUnrecoverableError
263
+ isSDAMUnrecoverableError,
264
+ isNodeShuttingDownError
248
265
  };
package/lib/core/index.js CHANGED
@@ -20,6 +20,7 @@ module.exports = {
20
20
  MongoNetworkError: require('./error').MongoNetworkError,
21
21
  MongoParseError: require('./error').MongoParseError,
22
22
  MongoTimeoutError: require('./error').MongoTimeoutError,
23
+ MongoServerSelectionError: require('./error').MongoServerSelectionError,
23
24
  MongoWriteConcernError: require('./error').MongoWriteConcernError,
24
25
  mongoErrorContextSymbol: require('./error').mongoErrorContextSymbol,
25
26
  // Core