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.
- package/HISTORY.md +110 -18
- package/lib/admin.js +1 -0
- package/lib/bulk/common.js +48 -4
- package/lib/change_stream.js +7 -3
- package/lib/cmap/connection.js +9 -6
- package/lib/collection.js +61 -84
- package/lib/core/auth/auth_provider.js +29 -132
- package/lib/core/auth/defaultAuthProviders.js +2 -2
- package/lib/core/auth/gssapi.js +69 -219
- 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 +30 -39
- package/lib/core/error.js +82 -8
- package/lib/core/sdam/common.js +8 -0
- package/lib/core/sdam/monitor.js +240 -79
- package/lib/core/sdam/server.js +82 -17
- package/lib/core/sdam/server_description.js +47 -2
- package/lib/core/sdam/topology.js +43 -32
- package/lib/core/sdam/topology_description.js +21 -3
- package/lib/core/sessions.js +14 -16
- package/lib/core/topologies/mongos.js +18 -6
- package/lib/core/topologies/read_preference.js +71 -7
- 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 +2 -10
- 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 +7 -6
- 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 +1 -22
- package/lib/operations/command.js +2 -3
- package/lib/operations/command_v2.js +8 -7
- 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 +91 -35
- package/lib/operations/db_ops.js +1 -2
- package/lib/operations/find.js +9 -3
- 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/geo_haystack_search.js +2 -2
- package/lib/operations/map_reduce.js +3 -3
- package/lib/operations/operation.js +2 -1
- package/lib/operations/re_index.js +22 -17
- package/lib/operations/replace_one.js +11 -4
- package/lib/operations/run_command.js +19 -0
- 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/topologies/topology_base.js +4 -4
- package/lib/utils.js +18 -60
- package/lib/write_concern.js +10 -0
- package/package.json +2 -2
- package/lib/core/auth/sspi.js +0 -131
- package/lib/operations/create_index.js +0 -92
package/lib/core/sdam/common.js
CHANGED
|
@@ -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
|
};
|
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,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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
120
|
+
reset() {
|
|
117
121
|
if (isInCloseState(this)) {
|
|
118
122
|
return;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
stateTransition(this, STATE_CLOSING);
|
|
122
|
-
this
|
|
123
|
-
if (this[kMonitorId]) {
|
|
124
|
-
this[kMonitorId].stop();
|
|
125
|
-
this[kMonitorId] = null;
|
|
126
|
-
}
|
|
126
|
+
resetMonitorState(this);
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
monitor.
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
161
|
-
|
|
203
|
+
const cmd = isAwaitable
|
|
204
|
+
? { ismaster: true, maxAwaitTimeMS, topologyVersion: makeTopologyVersion(topologyVersion) }
|
|
205
|
+
: { ismaster: true };
|
|
162
206
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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 (
|
|
185
|
-
|
|
186
|
-
failureHandler(err);
|
|
251
|
+
if (conn && isInCloseState(monitor)) {
|
|
252
|
+
conn.destroy({ force: true });
|
|
187
253
|
return;
|
|
188
254
|
}
|
|
189
255
|
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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,
|
|
216
|
-
if (
|
|
217
|
-
|
|
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
|
-
//
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
314
|
+
done();
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
}
|
|
230
318
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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 = {
|
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 = [
|
|
@@ -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:
|
|
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(
|
|
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, {
|
|
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
|
|
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 (!
|
|
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
|
|
484
|
-
if
|
|
485
|
-
|
|
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
|
-
|
|
489
|
-
|
|
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
|
|