mongodb 3.5.9 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/HISTORY.md +110 -18
  2. package/lib/admin.js +1 -0
  3. package/lib/bulk/common.js +48 -4
  4. package/lib/change_stream.js +7 -3
  5. package/lib/cmap/connection.js +9 -6
  6. package/lib/collection.js +61 -84
  7. package/lib/core/auth/auth_provider.js +29 -132
  8. package/lib/core/auth/defaultAuthProviders.js +2 -2
  9. package/lib/core/auth/gssapi.js +69 -219
  10. package/lib/core/auth/mongo_credentials.js +29 -3
  11. package/lib/core/auth/mongocr.js +6 -12
  12. package/lib/core/auth/mongodb_aws.js +256 -0
  13. package/lib/core/auth/plain.js +5 -12
  14. package/lib/core/auth/scram.js +229 -212
  15. package/lib/core/auth/x509.js +25 -16
  16. package/lib/core/connection/connect.js +97 -161
  17. package/lib/core/connection/connection.js +71 -3
  18. package/lib/core/connection/pool.js +2 -2
  19. package/lib/core/cursor.js +30 -39
  20. package/lib/core/error.js +82 -8
  21. package/lib/core/sdam/common.js +8 -0
  22. package/lib/core/sdam/monitor.js +240 -79
  23. package/lib/core/sdam/server.js +82 -17
  24. package/lib/core/sdam/server_description.js +47 -2
  25. package/lib/core/sdam/topology.js +43 -32
  26. package/lib/core/sdam/topology_description.js +21 -3
  27. package/lib/core/sessions.js +14 -16
  28. package/lib/core/topologies/mongos.js +18 -6
  29. package/lib/core/topologies/read_preference.js +71 -7
  30. package/lib/core/topologies/replset.js +4 -4
  31. package/lib/core/topologies/server.js +1 -1
  32. package/lib/core/topologies/shared.js +39 -16
  33. package/lib/core/uri_parser.js +41 -6
  34. package/lib/core/utils.js +30 -0
  35. package/lib/core/wireprotocol/command.js +2 -10
  36. package/lib/core/wireprotocol/constants.js +2 -2
  37. package/lib/core/wireprotocol/query.js +4 -0
  38. package/lib/cursor.js +0 -1
  39. package/lib/db.js +7 -6
  40. package/lib/error.js +6 -1
  41. package/lib/gridfs-stream/download.js +13 -2
  42. package/lib/mongo_client.js +6 -4
  43. package/lib/operations/collection_ops.js +1 -22
  44. package/lib/operations/command.js +2 -3
  45. package/lib/operations/command_v2.js +8 -7
  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 +91 -35
  50. package/lib/operations/db_ops.js +1 -2
  51. package/lib/operations/find.js +9 -3
  52. package/lib/operations/find_and_modify.js +17 -0
  53. package/lib/operations/find_one_and_delete.js +5 -0
  54. package/lib/operations/find_one_and_replace.js +13 -0
  55. package/lib/operations/find_one_and_update.js +13 -0
  56. package/lib/operations/geo_haystack_search.js +2 -2
  57. package/lib/operations/map_reduce.js +3 -3
  58. package/lib/operations/operation.js +2 -1
  59. package/lib/operations/re_index.js +22 -17
  60. package/lib/operations/replace_one.js +11 -4
  61. package/lib/operations/run_command.js +19 -0
  62. package/lib/operations/update_many.js +5 -0
  63. package/lib/operations/update_one.js +5 -0
  64. package/lib/operations/validate_collection.js +1 -2
  65. package/lib/topologies/mongos.js +1 -1
  66. package/lib/topologies/replset.js +1 -1
  67. package/lib/topologies/server.js +1 -1
  68. package/lib/topologies/topology_base.js +4 -4
  69. package/lib/utils.js +18 -60
  70. package/lib/write_concern.js +10 -0
  71. package/package.json +2 -2
  72. package/lib/core/auth/sspi.js +0 -131
  73. package/lib/operations/create_index.js +0 -92
@@ -28,6 +28,13 @@ const ServerType = {
28
28
  Unknown: 'Unknown'
29
29
  };
30
30
 
31
+ // helper to get a server's type that works for both legacy and unified topologies
32
+ function serverType(server) {
33
+ let description = server.s.description || server.s.serverDescription;
34
+ if (description.topologyType === TopologyType.Single) return description.servers[0].type;
35
+ return description.type;
36
+ }
37
+
31
38
  const TOPOLOGY_DEFAULTS = {
32
39
  useUnifiedTopology: true,
33
40
  localThresholdMS: 15,
@@ -54,6 +61,7 @@ module.exports = {
54
61
  TOPOLOGY_DEFAULTS,
55
62
  TopologyType,
56
63
  ServerType,
64
+ serverType,
57
65
  drainTimerQueue,
58
66
  clearAndRemoveTimerFrom
59
67
  };
@@ -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,28 +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
- const addressParts = server.description.address.split(':');
70
- this.connectOptions = Object.freeze(
71
- Object.assign(
72
- {
73
- id: '<monitor>',
74
- host: addressParts[0],
75
- port: parseInt(addressParts[1], 10),
76
- bson: server.s.bson,
77
- connectionType: Connection
78
- },
79
- server.s.options,
80
- this.options,
81
-
82
- // force BSON serialization options
83
- {
84
- raw: false,
85
- promoteLongs: true,
86
- promoteValues: true,
87
- promoteBuffers: true
88
- }
89
- )
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
+ }
90
90
  );
91
+
92
+ // ensure no authentication is used for monitoring
93
+ delete connectOptions.credentials;
94
+ this.connectOptions = Object.freeze(connectOptions);
91
95
  }
92
96
 
93
97
  connect() {
@@ -113,88 +117,165 @@ class Monitor extends EventEmitter {
113
117
  this[kMonitorId].wake();
114
118
  }
115
119
 
116
- close() {
120
+ reset() {
117
121
  if (isInCloseState(this)) {
118
122
  return;
119
123
  }
120
124
 
121
125
  stateTransition(this, STATE_CLOSING);
122
- this[kCancellationToken].emit('cancel');
123
- if (this[kMonitorId]) {
124
- this[kMonitorId].stop();
125
- this[kMonitorId] = null;
126
- }
126
+ resetMonitorState(this);
127
127
 
128
- if (this[kConnection]) {
129
- 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;
130
143
  }
131
144
 
145
+ stateTransition(this, STATE_CLOSING);
146
+ resetMonitorState(this);
147
+
148
+ // close monitor
132
149
  this.emit('close');
133
150
  stateTransition(this, STATE_CLOSED);
134
151
  }
135
152
  }
136
153
 
137
- function checkServer(monitor, callback) {
138
- if (monitor[kConnection] && monitor[kConnection].closed) {
139
- monitor[kConnection] = undefined;
154
+ function resetMonitorState(monitor) {
155
+ stateTransition(monitor, STATE_CLOSING);
156
+ if (monitor[kMonitorId]) {
157
+ monitor[kMonitorId].stop();
158
+ monitor[kMonitorId] = null;
140
159
  }
141
160
 
142
- 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();
143
179
  monitor.emit('serverHeartbeatStarted', new ServerHeartbeatStartedEvent(monitor.address));
144
180
 
145
181
  function failureHandler(err) {
182
+ if (monitor[kConnection]) {
183
+ monitor[kConnection].destroy({ force: true });
184
+ monitor[kConnection] = undefined;
185
+ }
186
+
146
187
  monitor.emit(
147
188
  'serverHeartbeatFailed',
148
189
  new ServerHeartbeatFailedEvent(calculateDurationInMs(start), err, monitor.address)
149
190
  );
150
191
 
192
+ monitor.emit('resetServer', err);
193
+ monitor.emit('resetConnectionPool');
151
194
  callback(err);
152
195
  }
153
196
 
154
- function successHandler(isMaster) {
155
- monitor.emit(
156
- 'serverHeartbeatSucceeded',
157
- new ServerHeartbeatSucceededEvent(calculateDurationInMs(start), isMaster, monitor.address)
158
- );
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;
159
202
 
160
- return callback(undefined, isMaster);
161
- }
203
+ const cmd = isAwaitable
204
+ ? { ismaster: true, maxAwaitTimeMS, topologyVersion: makeTopologyVersion(topologyVersion) }
205
+ : { ismaster: true };
162
206
 
163
- if (monitor[kConnection] != null) {
164
- const connectTimeoutMS = monitor.options.connectTimeoutMS;
165
- monitor[kConnection].command(
166
- 'admin.$cmd',
167
- { ismaster: true },
168
- { socketTimeout: connectTimeoutMS },
169
- (err, result) => {
170
- if (err) {
171
- failureHandler(err);
172
- 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;
173
240
  }
174
241
 
175
- successHandler(result.result);
242
+ callback(undefined, isMaster);
176
243
  }
177
- );
244
+ });
178
245
 
179
246
  return;
180
247
  }
181
248
 
182
249
  // connecting does an implicit `ismaster`
183
250
  connect(monitor.connectOptions, monitor[kCancellationToken], (err, conn) => {
184
- if (err) {
185
- monitor[kConnection] = undefined;
186
- failureHandler(err);
251
+ if (conn && isInCloseState(monitor)) {
252
+ conn.destroy({ force: true });
187
253
  return;
188
254
  }
189
255
 
190
- if (isInCloseState(monitor)) {
191
- conn.destroy({ force: true });
192
- 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);
193
265
  return;
194
266
  }
195
267
 
196
268
  monitor[kConnection] = conn;
197
- 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);
198
279
  });
199
280
  }
200
281
 
@@ -212,31 +293,111 @@ function monitorServer(monitor) {
212
293
  // TODO: the next line is a legacy event, remove in v4
213
294
  process.nextTick(() => monitor.emit('monitoring', monitor[kServer]));
214
295
 
215
- checkServer(monitor, e0 => {
216
- if (e0 == null) {
217
- 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
+ }
218
303
  }
219
304
 
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();
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
+ });
224
312
  }
225
313
 
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');
314
+ done();
315
+ });
316
+ };
317
+ }
230
318
 
231
- checkServer(monitor, e1 => {
232
- if (e1) {
233
- monitor.emit('resetServer', e1);
234
- }
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
+ }
235
366
 
236
- done();
237
- });
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);
238
387
  });
239
- };
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
+ });
240
401
  }
241
402
 
242
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 = [
@@ -110,9 +115,8 @@ class Server extends EventEmitter {
110
115
 
111
116
  // create the connection pool
112
117
  // NOTE: this used to happen in `connect`, we supported overriding pool options there
113
- const addressParts = this.description.address.split(':');
114
118
  const poolOptions = Object.assign(
115
- { host: addressParts[0], port: parseInt(addressParts[1], 10), bson: this.s.bson },
119
+ { host: this.description.host, port: this.description.port, bson: this.s.bson },
116
120
  options
117
121
  );
118
122
 
@@ -278,7 +282,7 @@ class Server extends EventEmitter {
278
282
  return cb(err);
279
283
  }
280
284
 
281
- conn.command(ns, cmd, options, makeOperationHandler(this, options, cb));
285
+ conn.command(ns, cmd, options, makeOperationHandler(this, conn, cmd, options, cb));
282
286
  }, callback);
283
287
  }
284
288
 
@@ -302,7 +306,7 @@ class Server extends EventEmitter {
302
306
  return cb(err);
303
307
  }
304
308
 
305
- conn.query(ns, cmd, cursorState, options, makeOperationHandler(this, options, cb));
309
+ conn.query(ns, cmd, cursorState, options, makeOperationHandler(this, conn, cmd, options, cb));
306
310
  }, callback);
307
311
  }
308
312
 
@@ -326,7 +330,13 @@ class Server extends EventEmitter {
326
330
  return cb(err);
327
331
  }
328
332
 
329
- 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
+ );
330
340
  }, callback);
331
341
  }
332
342
 
@@ -352,7 +362,7 @@ class Server extends EventEmitter {
352
362
  return cb(err);
353
363
  }
354
364
 
355
- conn.killCursors(ns, cursorState, makeOperationHandler(this, null, cb));
365
+ conn.killCursors(ns, cursorState, makeOperationHandler(this, conn, null, undefined, cb));
356
366
  }, callback);
357
367
  }
358
368
 
@@ -414,6 +424,14 @@ Object.defineProperty(Server.prototype, 'clusterTime', {
414
424
  }
415
425
  });
416
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
+
417
435
  function calculateRoundTripTime(oldRtt, duration) {
418
436
  if (oldRtt === -1) {
419
437
  return duration;
@@ -448,6 +466,13 @@ function executeWriteOperation(args, options, callback) {
448
466
  callback(new MongoError(`server ${server.name} does not support collation`));
449
467
  return;
450
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
+ }
451
476
 
452
477
  server.s.pool.withConnection((err, conn, cb) => {
453
478
  if (err) {
@@ -455,38 +480,78 @@ function executeWriteOperation(args, options, callback) {
455
480
  return cb(err);
456
481
  }
457
482
 
458
- conn[op](ns, ops, options, makeOperationHandler(server, options, cb));
483
+ conn[op](ns, ops, options, makeOperationHandler(server, conn, ops, options, cb));
459
484
  }, callback);
460
485
  }
461
486
 
462
487
  function markServerUnknown(server, error) {
488
+ if (error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError)) {
489
+ server[kMonitor].reset();
490
+ }
491
+
463
492
  server.emit(
464
493
  'descriptionReceived',
465
- 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
+ })
466
499
  );
467
500
  }
468
501
 
469
- 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) {
470
518
  const session = options && options.session;
471
519
 
472
520
  return function handleOperationResult(err, result) {
473
- if (err) {
521
+ if (err && !connectionIsStale(server.s.pool, connection)) {
474
522
  if (err instanceof MongoNetworkError) {
475
523
  if (session && !session.hasEnded) {
476
524
  session.serverSession.isDirty = true;
477
525
  }
478
526
 
479
- if (!isNetworkTimeoutError(err)) {
527
+ if (supportsRetryableWrites(server) && !inActiveTransaction(session, cmd)) {
528
+ err.addErrorLabel('RetryableWriteError');
529
+ }
530
+
531
+ if (!(err instanceof MongoNetworkTimeoutError) || isNetworkErrorBeforeHandshake(err)) {
480
532
  markServerUnknown(server, err);
481
533
  server.s.pool.clear();
482
534
  }
483
- } else if (isSDAMUnrecoverableError(err)) {
484
- if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(err)) {
485
- 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');
486
543
  }
487
544
 
488
- markServerUnknown(server, err);
489
- 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
+ }
490
555
  }
491
556
  }
492
557