mongodb 3.5.11 → 3.6.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 (68) hide show
  1. package/HISTORY.md +42 -21
  2. package/lib/admin.js +1 -0
  3. package/lib/bulk/common.js +48 -4
  4. package/lib/change_stream.js +0 -1
  5. package/lib/cmap/connection.js +9 -6
  6. package/lib/cmap/connection_pool.js +4 -10
  7. package/lib/collection.js +59 -81
  8. package/lib/core/auth/auth_provider.js +29 -132
  9. package/lib/core/auth/defaultAuthProviders.js +2 -2
  10. package/lib/core/auth/gssapi.js +124 -214
  11. package/lib/core/auth/mongo_credentials.js +29 -3
  12. package/lib/core/auth/mongocr.js +6 -12
  13. package/lib/core/auth/mongodb_aws.js +256 -0
  14. package/lib/core/auth/plain.js +5 -12
  15. package/lib/core/auth/scram.js +229 -212
  16. package/lib/core/auth/x509.js +25 -16
  17. package/lib/core/connection/connect.js +97 -161
  18. package/lib/core/connection/connection.js +71 -3
  19. package/lib/core/connection/pool.js +2 -2
  20. package/lib/core/cursor.js +18 -11
  21. package/lib/core/error.js +82 -8
  22. package/lib/core/sdam/common.js +8 -0
  23. package/lib/core/sdam/monitor.js +240 -78
  24. package/lib/core/sdam/server.js +81 -15
  25. package/lib/core/sdam/server_description.js +37 -2
  26. package/lib/core/sdam/topology.js +41 -8
  27. package/lib/core/sdam/topology_description.js +21 -3
  28. package/lib/core/sessions.js +38 -51
  29. package/lib/core/topologies/mongos.js +18 -6
  30. package/lib/core/topologies/read_preference.js +15 -3
  31. package/lib/core/topologies/replset.js +4 -4
  32. package/lib/core/topologies/server.js +1 -1
  33. package/lib/core/topologies/shared.js +39 -16
  34. package/lib/core/uri_parser.js +41 -6
  35. package/lib/core/utils.js +30 -0
  36. package/lib/core/wireprotocol/command.js +1 -4
  37. package/lib/core/wireprotocol/constants.js +2 -2
  38. package/lib/core/wireprotocol/query.js +4 -0
  39. package/lib/cursor.js +0 -1
  40. package/lib/db.js +6 -5
  41. package/lib/error.js +6 -1
  42. package/lib/gridfs-stream/download.js +13 -2
  43. package/lib/mongo_client.js +6 -4
  44. package/lib/operations/collection_ops.js +0 -20
  45. package/lib/operations/command_v2.js +1 -1
  46. package/lib/operations/common_functions.js +3 -0
  47. package/lib/operations/connect.js +11 -14
  48. package/lib/operations/create_collection.js +37 -52
  49. package/lib/operations/create_indexes.js +111 -35
  50. package/lib/operations/find.js +7 -1
  51. package/lib/operations/find_and_modify.js +17 -0
  52. package/lib/operations/find_one_and_delete.js +5 -0
  53. package/lib/operations/find_one_and_replace.js +13 -0
  54. package/lib/operations/find_one_and_update.js +13 -0
  55. package/lib/operations/map_reduce.js +1 -1
  56. package/lib/operations/re_index.js +22 -17
  57. package/lib/operations/replace_one.js +11 -4
  58. package/lib/operations/update_many.js +5 -0
  59. package/lib/operations/update_one.js +5 -0
  60. package/lib/operations/validate_collection.js +1 -2
  61. package/lib/topologies/mongos.js +1 -1
  62. package/lib/topologies/replset.js +1 -1
  63. package/lib/topologies/server.js +1 -1
  64. package/lib/utils.js +18 -1
  65. package/lib/write_concern.js +10 -0
  66. package/package.json +1 -1
  67. package/lib/core/auth/sspi.js +0 -131
  68. package/lib/operations/create_index.js +0 -92
@@ -6,7 +6,8 @@ const connect = require('../connection/connect');
6
6
  const Connection = require('../../cmap/connection').Connection;
7
7
  const common = require('./common');
8
8
  const makeStateMachine = require('../utils').makeStateMachine;
9
- const MongoError = require('../error').MongoError;
9
+ const MongoNetworkError = require('../error').MongoNetworkError;
10
+ const BSON = require('../connection/utils').retrieveBSON();
10
11
  const makeInterruptableAsyncInterval = require('../../utils').makeInterruptableAsyncInterval;
11
12
  const calculateDurationInMs = require('../../utils').calculateDurationInMs;
12
13
  const now = require('../../utils').now;
@@ -20,13 +21,15 @@ const kServer = Symbol('server');
20
21
  const kMonitorId = Symbol('monitorId');
21
22
  const kConnection = Symbol('connection');
22
23
  const kCancellationToken = Symbol('cancellationToken');
24
+ const kRTTPinger = Symbol('rttPinger');
25
+ const kRoundTripTime = Symbol('roundTripTime');
23
26
 
24
27
  const STATE_CLOSED = common.STATE_CLOSED;
25
28
  const STATE_CLOSING = common.STATE_CLOSING;
26
29
  const STATE_IDLE = 'idle';
27
30
  const STATE_MONITORING = 'monitoring';
28
31
  const stateTransition = makeStateMachine({
29
- [STATE_CLOSING]: [STATE_CLOSING, STATE_CLOSED],
32
+ [STATE_CLOSING]: [STATE_CLOSING, STATE_IDLE, STATE_CLOSED],
30
33
  [STATE_CLOSED]: [STATE_CLOSED, STATE_MONITORING],
31
34
  [STATE_IDLE]: [STATE_IDLE, STATE_MONITORING, STATE_CLOSING],
32
35
  [STATE_MONITORING]: [STATE_MONITORING, STATE_IDLE, STATE_CLOSING]
@@ -66,27 +69,29 @@ class Monitor extends EventEmitter {
66
69
  });
67
70
 
68
71
  // TODO: refactor this to pull it directly from the pool, requires new ConnectionPool integration
69
- this.connectOptions = Object.freeze(
70
- Object.assign(
71
- {
72
- id: '<monitor>',
73
- host: server.description.host,
74
- port: server.description.port,
75
- bson: server.s.bson,
76
- connectionType: Connection
77
- },
78
- server.s.options,
79
- this.options,
80
-
81
- // force BSON serialization options
82
- {
83
- raw: false,
84
- promoteLongs: true,
85
- promoteValues: true,
86
- promoteBuffers: true
87
- }
88
- )
72
+ const connectOptions = Object.assign(
73
+ {
74
+ id: '<monitor>',
75
+ host: server.description.host,
76
+ port: server.description.port,
77
+ bson: server.s.bson,
78
+ connectionType: Connection
79
+ },
80
+ server.s.options,
81
+ this.options,
82
+
83
+ // force BSON serialization options
84
+ {
85
+ raw: false,
86
+ promoteLongs: true,
87
+ promoteValues: true,
88
+ promoteBuffers: true
89
+ }
89
90
  );
91
+
92
+ // ensure no authentication is used for monitoring
93
+ delete connectOptions.credentials;
94
+ this.connectOptions = Object.freeze(connectOptions);
90
95
  }
91
96
 
92
97
  connect() {
@@ -112,88 +117,165 @@ class Monitor extends EventEmitter {
112
117
  this[kMonitorId].wake();
113
118
  }
114
119
 
115
- close() {
120
+ reset() {
116
121
  if (isInCloseState(this)) {
117
122
  return;
118
123
  }
119
124
 
120
125
  stateTransition(this, STATE_CLOSING);
121
- this[kCancellationToken].emit('cancel');
122
- if (this[kMonitorId]) {
123
- this[kMonitorId].stop();
124
- this[kMonitorId] = null;
125
- }
126
+ resetMonitorState(this);
126
127
 
127
- if (this[kConnection]) {
128
- this[kConnection].destroy({ force: true });
128
+ // restart monitor
129
+ stateTransition(this, STATE_IDLE);
130
+
131
+ // restart monitoring
132
+ const heartbeatFrequencyMS = this.options.heartbeatFrequencyMS;
133
+ const minHeartbeatFrequencyMS = this.options.minHeartbeatFrequencyMS;
134
+ this[kMonitorId] = makeInterruptableAsyncInterval(monitorServer(this), {
135
+ interval: heartbeatFrequencyMS,
136
+ minInterval: minHeartbeatFrequencyMS
137
+ });
138
+ }
139
+
140
+ close() {
141
+ if (isInCloseState(this)) {
142
+ return;
129
143
  }
130
144
 
145
+ stateTransition(this, STATE_CLOSING);
146
+ resetMonitorState(this);
147
+
148
+ // close monitor
131
149
  this.emit('close');
132
150
  stateTransition(this, STATE_CLOSED);
133
151
  }
134
152
  }
135
153
 
136
- function checkServer(monitor, callback) {
137
- if (monitor[kConnection] && monitor[kConnection].closed) {
138
- monitor[kConnection] = undefined;
154
+ function resetMonitorState(monitor) {
155
+ stateTransition(monitor, STATE_CLOSING);
156
+ if (monitor[kMonitorId]) {
157
+ monitor[kMonitorId].stop();
158
+ monitor[kMonitorId] = null;
139
159
  }
140
160
 
141
- const start = now();
161
+ if (monitor[kRTTPinger]) {
162
+ monitor[kRTTPinger].close();
163
+ monitor[kRTTPinger] = undefined;
164
+ }
165
+
166
+ monitor[kCancellationToken].emit('cancel');
167
+ if (monitor[kMonitorId]) {
168
+ clearTimeout(monitor[kMonitorId]);
169
+ monitor[kMonitorId] = undefined;
170
+ }
171
+
172
+ if (monitor[kConnection]) {
173
+ monitor[kConnection].destroy({ force: true });
174
+ }
175
+ }
176
+
177
+ function checkServer(monitor, callback) {
178
+ let start = now();
142
179
  monitor.emit('serverHeartbeatStarted', new ServerHeartbeatStartedEvent(monitor.address));
143
180
 
144
181
  function failureHandler(err) {
182
+ if (monitor[kConnection]) {
183
+ monitor[kConnection].destroy({ force: true });
184
+ monitor[kConnection] = undefined;
185
+ }
186
+
145
187
  monitor.emit(
146
188
  'serverHeartbeatFailed',
147
189
  new ServerHeartbeatFailedEvent(calculateDurationInMs(start), err, monitor.address)
148
190
  );
149
191
 
192
+ monitor.emit('resetServer', err);
193
+ monitor.emit('resetConnectionPool');
150
194
  callback(err);
151
195
  }
152
196
 
153
- function successHandler(isMaster) {
154
- monitor.emit(
155
- 'serverHeartbeatSucceeded',
156
- new ServerHeartbeatSucceededEvent(calculateDurationInMs(start), isMaster, monitor.address)
157
- );
197
+ if (monitor[kConnection] != null && !monitor[kConnection].closed) {
198
+ const connectTimeoutMS = monitor.options.connectTimeoutMS;
199
+ const maxAwaitTimeMS = monitor.options.heartbeatFrequencyMS;
200
+ const topologyVersion = monitor[kServer].description.topologyVersion;
201
+ const isAwaitable = topologyVersion != null;
158
202
 
159
- return callback(undefined, isMaster);
160
- }
203
+ const cmd = isAwaitable
204
+ ? { ismaster: true, maxAwaitTimeMS, topologyVersion: makeTopologyVersion(topologyVersion) }
205
+ : { ismaster: true };
161
206
 
162
- if (monitor[kConnection] != null) {
163
- const connectTimeoutMS = monitor.options.connectTimeoutMS;
164
- monitor[kConnection].command(
165
- 'admin.$cmd',
166
- { ismaster: true },
167
- { socketTimeout: connectTimeoutMS },
168
- (err, result) => {
169
- if (err) {
170
- failureHandler(err);
171
- return;
207
+ const options = isAwaitable
208
+ ? { socketTimeout: connectTimeoutMS + maxAwaitTimeMS, exhaustAllowed: true }
209
+ : { socketTimeout: connectTimeoutMS };
210
+
211
+ if (isAwaitable && monitor[kRTTPinger] == null) {
212
+ monitor[kRTTPinger] = new RTTPinger(monitor[kCancellationToken], monitor.connectOptions);
213
+ }
214
+
215
+ monitor[kConnection].command('admin.$cmd', cmd, options, (err, result) => {
216
+ if (err) {
217
+ failureHandler(err);
218
+ return;
219
+ }
220
+
221
+ const isMaster = result.result;
222
+ const duration = isAwaitable
223
+ ? monitor[kRTTPinger].roundTripTime
224
+ : calculateDurationInMs(start);
225
+
226
+ monitor.emit(
227
+ 'serverHeartbeatSucceeded',
228
+ new ServerHeartbeatSucceededEvent(duration, isMaster, monitor.address)
229
+ );
230
+
231
+ // if we are using the streaming protocol then we immediately issue another `started`
232
+ // event, otherwise the "check" is complete and return to the main monitor loop
233
+ if (isAwaitable && isMaster.topologyVersion) {
234
+ monitor.emit('serverHeartbeatStarted', new ServerHeartbeatStartedEvent(monitor.address));
235
+ start = now();
236
+ } else {
237
+ if (monitor[kRTTPinger]) {
238
+ monitor[kRTTPinger].close();
239
+ monitor[kRTTPinger] = undefined;
172
240
  }
173
241
 
174
- successHandler(result.result);
242
+ callback(undefined, isMaster);
175
243
  }
176
- );
244
+ });
177
245
 
178
246
  return;
179
247
  }
180
248
 
181
249
  // connecting does an implicit `ismaster`
182
250
  connect(monitor.connectOptions, monitor[kCancellationToken], (err, conn) => {
183
- if (err) {
184
- monitor[kConnection] = undefined;
185
- failureHandler(err);
251
+ if (conn && isInCloseState(monitor)) {
252
+ conn.destroy({ force: true });
186
253
  return;
187
254
  }
188
255
 
189
- if (isInCloseState(monitor)) {
190
- conn.destroy({ force: true });
191
- failureHandler(new MongoError('monitor was destroyed'));
256
+ if (err) {
257
+ monitor[kConnection] = undefined;
258
+
259
+ // we already reset the connection pool on network errors in all cases
260
+ if (!(err instanceof MongoNetworkError)) {
261
+ monitor.emit('resetConnectionPool');
262
+ }
263
+
264
+ failureHandler(err);
192
265
  return;
193
266
  }
194
267
 
195
268
  monitor[kConnection] = conn;
196
- successHandler(conn.ismaster);
269
+ monitor.emit(
270
+ 'serverHeartbeatSucceeded',
271
+ new ServerHeartbeatSucceededEvent(
272
+ calculateDurationInMs(start),
273
+ conn.ismaster,
274
+ monitor.address
275
+ )
276
+ );
277
+
278
+ callback(undefined, conn.ismaster);
197
279
  });
198
280
  }
199
281
 
@@ -211,31 +293,111 @@ function monitorServer(monitor) {
211
293
  // TODO: the next line is a legacy event, remove in v4
212
294
  process.nextTick(() => monitor.emit('monitoring', monitor[kServer]));
213
295
 
214
- checkServer(monitor, e0 => {
215
- if (e0 == null) {
216
- return done();
296
+ checkServer(monitor, (err, isMaster) => {
297
+ if (err) {
298
+ // otherwise an error occured on initial discovery, also bail
299
+ if (monitor[kServer].description.type === ServerType.Unknown) {
300
+ monitor.emit('resetServer', err);
301
+ return done();
302
+ }
217
303
  }
218
304
 
219
- // otherwise an error occured on initial discovery, also bail
220
- if (monitor[kServer].description.type === ServerType.Unknown) {
221
- monitor.emit('resetServer', e0);
222
- return done();
305
+ // if the check indicates streaming is supported, immediately reschedule monitoring
306
+ if (isMaster && isMaster.topologyVersion) {
307
+ setTimeout(() => {
308
+ if (!isInCloseState(monitor)) {
309
+ monitor[kMonitorId].wake();
310
+ }
311
+ });
223
312
  }
224
313
 
225
- // According to the SDAM specification's "Network error during server check" section, if
226
- // an ismaster call fails we reset the server's pool. If a server was once connected,
227
- // change its type to `Unknown` only after retrying once.
228
- monitor.emit('resetConnectionPool');
314
+ done();
315
+ });
316
+ };
317
+ }
229
318
 
230
- checkServer(monitor, e1 => {
231
- if (e1) {
232
- monitor.emit('resetServer', e1);
233
- }
319
+ function makeTopologyVersion(tv) {
320
+ return {
321
+ processId: tv.processId,
322
+ counter: BSON.Long.fromNumber(tv.counter)
323
+ };
324
+ }
325
+
326
+ class RTTPinger {
327
+ constructor(cancellationToken, options) {
328
+ this[kConnection] = null;
329
+ this[kCancellationToken] = cancellationToken;
330
+ this[kRoundTripTime] = 0;
331
+ this.closed = false;
332
+
333
+ const heartbeatFrequencyMS = options.heartbeatFrequencyMS;
334
+ this[kMonitorId] = setTimeout(() => measureRoundTripTime(this, options), heartbeatFrequencyMS);
335
+ }
336
+
337
+ get roundTripTime() {
338
+ return this[kRoundTripTime];
339
+ }
340
+
341
+ close() {
342
+ this.closed = true;
343
+
344
+ clearTimeout(this[kMonitorId]);
345
+ this[kMonitorId] = undefined;
346
+
347
+ if (this[kConnection]) {
348
+ this[kConnection].destroy({ force: true });
349
+ }
350
+ }
351
+ }
352
+
353
+ function measureRoundTripTime(rttPinger, options) {
354
+ const start = now();
355
+ const cancellationToken = rttPinger[kCancellationToken];
356
+ const heartbeatFrequencyMS = options.heartbeatFrequencyMS;
357
+ if (rttPinger.closed) {
358
+ return;
359
+ }
360
+
361
+ function measureAndReschedule(conn) {
362
+ if (rttPinger.closed) {
363
+ conn.destroy({ force: true });
364
+ return;
365
+ }
234
366
 
235
- done();
236
- });
367
+ if (rttPinger[kConnection] == null) {
368
+ rttPinger[kConnection] = conn;
369
+ }
370
+
371
+ rttPinger[kRoundTripTime] = calculateDurationInMs(start);
372
+ rttPinger[kMonitorId] = setTimeout(
373
+ () => measureRoundTripTime(rttPinger, options),
374
+ heartbeatFrequencyMS
375
+ );
376
+ }
377
+
378
+ if (rttPinger[kConnection] == null) {
379
+ connect(options, cancellationToken, (err, conn) => {
380
+ if (err) {
381
+ rttPinger[kConnection] = undefined;
382
+ rttPinger[kRoundTripTime] = 0;
383
+ return;
384
+ }
385
+
386
+ measureAndReschedule(conn);
237
387
  });
238
- };
388
+
389
+ return;
390
+ }
391
+
392
+ rttPinger[kConnection].command('admin.$cmd', { ismaster: 1 }, err => {
393
+ if (err) {
394
+ rttPinger[kConnection] = undefined;
395
+ rttPinger[kRoundTripTime] = 0;
396
+ return;
397
+ }
398
+
399
+ measureAndReschedule();
400
+ });
239
401
  }
240
402
 
241
403
  module.exports = {
@@ -7,17 +7,22 @@ const relayEvents = require('../utils').relayEvents;
7
7
  const BSON = require('../connection/utils').retrieveBSON();
8
8
  const Logger = require('../connection/logger');
9
9
  const ServerDescription = require('./server_description').ServerDescription;
10
+ const compareTopologyVersion = require('./server_description').compareTopologyVersion;
10
11
  const ReadPreference = require('../topologies/read_preference');
11
12
  const Monitor = require('./monitor').Monitor;
12
13
  const MongoNetworkError = require('../error').MongoNetworkError;
14
+ const MongoNetworkTimeoutError = require('../error').MongoNetworkTimeoutError;
13
15
  const collationNotSupported = require('../utils').collationNotSupported;
14
16
  const debugOptions = require('../connection/utils').debugOptions;
15
17
  const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
16
- const isNetworkTimeoutError = require('../error').isNetworkTimeoutError;
18
+ const isRetryableWriteError = require('../error').isRetryableWriteError;
17
19
  const isNodeShuttingDownError = require('../error').isNodeShuttingDownError;
20
+ const isNetworkErrorBeforeHandshake = require('../error').isNetworkErrorBeforeHandshake;
18
21
  const maxWireVersion = require('../utils').maxWireVersion;
19
22
  const makeStateMachine = require('../utils').makeStateMachine;
20
23
  const common = require('./common');
24
+ const ServerType = common.ServerType;
25
+ const isTransactionCommand = require('../transactions').isTransactionCommand;
21
26
 
22
27
  // Used for filtering out fields for logging
23
28
  const DEBUG_FIELDS = [
@@ -277,7 +282,7 @@ class Server extends EventEmitter {
277
282
  return cb(err);
278
283
  }
279
284
 
280
- conn.command(ns, cmd, options, makeOperationHandler(this, options, cb));
285
+ conn.command(ns, cmd, options, makeOperationHandler(this, conn, cmd, options, cb));
281
286
  }, callback);
282
287
  }
283
288
 
@@ -301,7 +306,7 @@ class Server extends EventEmitter {
301
306
  return cb(err);
302
307
  }
303
308
 
304
- conn.query(ns, cmd, cursorState, options, makeOperationHandler(this, options, cb));
309
+ conn.query(ns, cmd, cursorState, options, makeOperationHandler(this, conn, cmd, options, cb));
305
310
  }, callback);
306
311
  }
307
312
 
@@ -325,7 +330,13 @@ class Server extends EventEmitter {
325
330
  return cb(err);
326
331
  }
327
332
 
328
- conn.getMore(ns, cursorState, batchSize, options, makeOperationHandler(this, options, cb));
333
+ conn.getMore(
334
+ ns,
335
+ cursorState,
336
+ batchSize,
337
+ options,
338
+ makeOperationHandler(this, conn, null, options, cb)
339
+ );
329
340
  }, callback);
330
341
  }
331
342
 
@@ -351,7 +362,7 @@ class Server extends EventEmitter {
351
362
  return cb(err);
352
363
  }
353
364
 
354
- conn.killCursors(ns, cursorState, makeOperationHandler(this, null, cb));
365
+ conn.killCursors(ns, cursorState, makeOperationHandler(this, conn, null, undefined, cb));
355
366
  }, callback);
356
367
  }
357
368
 
@@ -413,6 +424,14 @@ Object.defineProperty(Server.prototype, 'clusterTime', {
413
424
  }
414
425
  });
415
426
 
427
+ function supportsRetryableWrites(server) {
428
+ return (
429
+ server.description.maxWireVersion >= 6 &&
430
+ server.description.logicalSessionTimeoutMinutes &&
431
+ server.description.type !== ServerType.Standalone
432
+ );
433
+ }
434
+
416
435
  function calculateRoundTripTime(oldRtt, duration) {
417
436
  if (oldRtt === -1) {
418
437
  return duration;
@@ -447,6 +466,13 @@ function executeWriteOperation(args, options, callback) {
447
466
  callback(new MongoError(`server ${server.name} does not support collation`));
448
467
  return;
449
468
  }
469
+ const unacknowledgedWrite = options.writeConcern && options.writeConcern.w === 0;
470
+ if (unacknowledgedWrite || maxWireVersion(server) < 5) {
471
+ if ((op === 'update' || op === 'remove') && ops.find(o => o.hint)) {
472
+ callback(new MongoError(`servers < 3.4 do not support hint on ${op}`));
473
+ return;
474
+ }
475
+ }
450
476
 
451
477
  server.s.pool.withConnection((err, conn, cb) => {
452
478
  if (err) {
@@ -454,38 +480,78 @@ function executeWriteOperation(args, options, callback) {
454
480
  return cb(err);
455
481
  }
456
482
 
457
- conn[op](ns, ops, options, makeOperationHandler(server, options, cb));
483
+ conn[op](ns, ops, options, makeOperationHandler(server, conn, ops, options, cb));
458
484
  }, callback);
459
485
  }
460
486
 
461
487
  function markServerUnknown(server, error) {
488
+ if (error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError)) {
489
+ server[kMonitor].reset();
490
+ }
491
+
462
492
  server.emit(
463
493
  'descriptionReceived',
464
- new ServerDescription(server.description.address, null, { error })
494
+ new ServerDescription(server.description.address, null, {
495
+ error,
496
+ topologyVersion:
497
+ error && error.topologyVersion ? error.topologyVersion : server.description.topologyVersion
498
+ })
465
499
  );
466
500
  }
467
501
 
468
- function makeOperationHandler(server, options, callback) {
502
+ function connectionIsStale(pool, connection) {
503
+ return connection.generation !== pool.generation;
504
+ }
505
+
506
+ function shouldHandleStateChangeError(server, err) {
507
+ const etv = err.topologyVersion;
508
+ const stv = server.description.topologyVersion;
509
+
510
+ return compareTopologyVersion(stv, etv) < 0;
511
+ }
512
+
513
+ function inActiveTransaction(session, cmd) {
514
+ return session && session.inTransaction() && !isTransactionCommand(cmd);
515
+ }
516
+
517
+ function makeOperationHandler(server, connection, cmd, options, callback) {
469
518
  const session = options && options.session;
470
519
 
471
520
  return function handleOperationResult(err, result) {
472
- if (err) {
521
+ if (err && !connectionIsStale(server.s.pool, connection)) {
473
522
  if (err instanceof MongoNetworkError) {
474
523
  if (session && !session.hasEnded) {
475
524
  session.serverSession.isDirty = true;
476
525
  }
477
526
 
478
- if (!isNetworkTimeoutError(err)) {
527
+ if (supportsRetryableWrites(server) && !inActiveTransaction(session, cmd)) {
528
+ err.addErrorLabel('RetryableWriteError');
529
+ }
530
+
531
+ if (!(err instanceof MongoNetworkTimeoutError) || isNetworkErrorBeforeHandshake(err)) {
479
532
  markServerUnknown(server, err);
480
533
  server.s.pool.clear();
481
534
  }
482
- } else if (isSDAMUnrecoverableError(err)) {
483
- if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(err)) {
484
- server.s.pool.clear();
535
+ } else {
536
+ // if pre-4.4 server, then add error label if its a retryable write error
537
+ if (
538
+ maxWireVersion(server) < 9 &&
539
+ isRetryableWriteError(err) &&
540
+ !inActiveTransaction(session, cmd)
541
+ ) {
542
+ err.addErrorLabel('RetryableWriteError');
485
543
  }
486
544
 
487
- markServerUnknown(server, err);
488
- process.nextTick(() => server.requestCheck());
545
+ if (isSDAMUnrecoverableError(err)) {
546
+ if (shouldHandleStateChangeError(server, err)) {
547
+ if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(err)) {
548
+ server.s.pool.clear();
549
+ }
550
+
551
+ markServerUnknown(server, err);
552
+ process.nextTick(() => server.requestCheck());
553
+ }
554
+ }
489
555
  }
490
556
  }
491
557
 
@@ -53,6 +53,8 @@ class ServerDescription {
53
53
  * @param {Object} [ismaster] An optional ismaster response for this server
54
54
  * @param {Object} [options] Optional settings
55
55
  * @param {Number} [options.roundTripTime] The round trip time to ping this server (in ms)
56
+ * @param {Error} [options.error] An Error used for better reporting debugging
57
+ * @param {any} [options.topologyVersion] The topologyVersion
56
58
  */
57
59
  constructor(address, ismaster, options) {
58
60
  options = options || {};
@@ -75,6 +77,7 @@ class ServerDescription {
75
77
  this.lastWriteDate = ismaster.lastWrite ? ismaster.lastWrite.lastWriteDate : null;
76
78
  this.opTime = ismaster.lastWrite ? ismaster.lastWrite.opTime : null;
77
79
  this.type = parseServerType(ismaster);
80
+ this.topologyVersion = options.topologyVersion || ismaster.topologyVersion;
78
81
 
79
82
  // direct mappings
80
83
  ISMASTER_FIELDS.forEach(field => {
@@ -131,6 +134,10 @@ class ServerDescription {
131
134
  * @return {Boolean}
132
135
  */
133
136
  equals(other) {
137
+ const topologyVersionsEqual =
138
+ this.topologyVersion === other.topologyVersion ||
139
+ compareTopologyVersion(this.topologyVersion, other.topologyVersion) === 0;
140
+
134
141
  return (
135
142
  other != null &&
136
143
  errorStrictEqual(this.error, other.error) &&
@@ -145,7 +152,8 @@ class ServerDescription {
145
152
  ? other.electionId && this.electionId.equals(other.electionId)
146
153
  : this.electionId === other.electionId) &&
147
154
  this.primary === other.primary &&
148
- this.logicalSessionTimeoutMinutes === other.logicalSessionTimeoutMinutes
155
+ this.logicalSessionTimeoutMinutes === other.logicalSessionTimeoutMinutes &&
156
+ topologyVersionsEqual
149
157
  );
150
158
  }
151
159
  }
@@ -186,7 +194,34 @@ function parseServerType(ismaster) {
186
194
  return ServerType.Standalone;
187
195
  }
188
196
 
197
+ /**
198
+ * Compares two topology versions.
199
+ *
200
+ * @param {object} lhs
201
+ * @param {object} rhs
202
+ * @returns A negative number if `lhs` is older than `rhs`; positive if `lhs` is newer than `rhs`; 0 if they are equivalent.
203
+ */
204
+ function compareTopologyVersion(lhs, rhs) {
205
+ if (lhs == null || rhs == null) {
206
+ return -1;
207
+ }
208
+
209
+ if (lhs.processId.equals(rhs.processId)) {
210
+ // TODO: handle counters as Longs
211
+ if (lhs.counter === rhs.counter) {
212
+ return 0;
213
+ } else if (lhs.counter < rhs.counter) {
214
+ return -1;
215
+ }
216
+
217
+ return 1;
218
+ }
219
+
220
+ return -1;
221
+ }
222
+
189
223
  module.exports = {
190
224
  ServerDescription,
191
- parseServerType
225
+ parseServerType,
226
+ compareTopologyVersion
192
227
  };