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.
- package/HISTORY.md +42 -21
- package/lib/admin.js +1 -0
- package/lib/bulk/common.js +48 -4
- package/lib/change_stream.js +0 -1
- package/lib/cmap/connection.js +9 -6
- package/lib/cmap/connection_pool.js +4 -10
- package/lib/collection.js +59 -81
- package/lib/core/auth/auth_provider.js +29 -132
- package/lib/core/auth/defaultAuthProviders.js +2 -2
- package/lib/core/auth/gssapi.js +124 -214
- package/lib/core/auth/mongo_credentials.js +29 -3
- package/lib/core/auth/mongocr.js +6 -12
- package/lib/core/auth/mongodb_aws.js +256 -0
- package/lib/core/auth/plain.js +5 -12
- package/lib/core/auth/scram.js +229 -212
- package/lib/core/auth/x509.js +25 -16
- package/lib/core/connection/connect.js +97 -161
- package/lib/core/connection/connection.js +71 -3
- package/lib/core/connection/pool.js +2 -2
- package/lib/core/cursor.js +18 -11
- package/lib/core/error.js +82 -8
- package/lib/core/sdam/common.js +8 -0
- package/lib/core/sdam/monitor.js +240 -78
- package/lib/core/sdam/server.js +81 -15
- package/lib/core/sdam/server_description.js +37 -2
- package/lib/core/sdam/topology.js +41 -8
- package/lib/core/sdam/topology_description.js +21 -3
- package/lib/core/sessions.js +38 -51
- package/lib/core/topologies/mongos.js +18 -6
- package/lib/core/topologies/read_preference.js +15 -3
- package/lib/core/topologies/replset.js +4 -4
- package/lib/core/topologies/server.js +1 -1
- package/lib/core/topologies/shared.js +39 -16
- package/lib/core/uri_parser.js +41 -6
- package/lib/core/utils.js +30 -0
- package/lib/core/wireprotocol/command.js +1 -4
- package/lib/core/wireprotocol/constants.js +2 -2
- package/lib/core/wireprotocol/query.js +4 -0
- package/lib/cursor.js +0 -1
- package/lib/db.js +6 -5
- package/lib/error.js +6 -1
- package/lib/gridfs-stream/download.js +13 -2
- package/lib/mongo_client.js +6 -4
- package/lib/operations/collection_ops.js +0 -20
- package/lib/operations/command_v2.js +1 -1
- package/lib/operations/common_functions.js +3 -0
- package/lib/operations/connect.js +11 -14
- package/lib/operations/create_collection.js +37 -52
- package/lib/operations/create_indexes.js +111 -35
- package/lib/operations/find.js +7 -1
- package/lib/operations/find_and_modify.js +17 -0
- package/lib/operations/find_one_and_delete.js +5 -0
- package/lib/operations/find_one_and_replace.js +13 -0
- package/lib/operations/find_one_and_update.js +13 -0
- package/lib/operations/map_reduce.js +1 -1
- package/lib/operations/re_index.js +22 -17
- package/lib/operations/replace_one.js +11 -4
- package/lib/operations/update_many.js +5 -0
- package/lib/operations/update_one.js +5 -0
- package/lib/operations/validate_collection.js +1 -2
- package/lib/topologies/mongos.js +1 -1
- package/lib/topologies/replset.js +1 -1
- package/lib/topologies/server.js +1 -1
- package/lib/utils.js +18 -1
- package/lib/write_concern.js +10 -0
- package/package.json +1 -1
- package/lib/core/auth/sspi.js +0 -131
- package/lib/operations/create_index.js +0 -92
package/lib/core/sdam/monitor.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
120
|
+
reset() {
|
|
116
121
|
if (isInCloseState(this)) {
|
|
117
122
|
return;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
stateTransition(this, STATE_CLOSING);
|
|
121
|
-
this
|
|
122
|
-
if (this[kMonitorId]) {
|
|
123
|
-
this[kMonitorId].stop();
|
|
124
|
-
this[kMonitorId] = null;
|
|
125
|
-
}
|
|
126
|
+
resetMonitorState(this);
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
monitor.
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
203
|
+
const cmd = isAwaitable
|
|
204
|
+
? { ismaster: true, maxAwaitTimeMS, topologyVersion: makeTopologyVersion(topologyVersion) }
|
|
205
|
+
: { ismaster: true };
|
|
161
206
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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 (
|
|
184
|
-
|
|
185
|
-
failureHandler(err);
|
|
251
|
+
if (conn && isInCloseState(monitor)) {
|
|
252
|
+
conn.destroy({ force: true });
|
|
186
253
|
return;
|
|
187
254
|
}
|
|
188
255
|
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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,
|
|
215
|
-
if (
|
|
216
|
-
|
|
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
|
-
//
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
314
|
+
done();
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
}
|
|
229
318
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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 = {
|
package/lib/core/sdam/server.js
CHANGED
|
@@ -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
|
|
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(
|
|
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, {
|
|
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
|
|
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 (!
|
|
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
|
|
483
|
-
if
|
|
484
|
-
|
|
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
|
-
|
|
488
|
-
|
|
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
|
};
|