mongodb 3.3.0-beta1 → 3.3.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 +117 -0
- package/index.js +1 -0
- package/lib/admin.js +6 -6
- package/lib/aggregation_cursor.js +180 -226
- package/lib/change_stream.js +233 -121
- package/lib/collection.js +74 -39
- package/lib/command_cursor.js +87 -165
- package/lib/core/connection/connect.js +1 -1
- package/lib/core/connection/msg.js +2 -1
- package/lib/core/connection/pool.js +66 -41
- package/lib/core/cursor.js +605 -461
- package/lib/core/error.js +70 -1
- package/lib/core/index.js +1 -1
- package/lib/core/sdam/server.js +109 -10
- package/lib/core/sdam/server_description.js +14 -0
- package/lib/core/sdam/topology.js +34 -14
- package/lib/core/sdam/topology_description.js +6 -14
- package/lib/core/sessions.js +68 -12
- package/lib/core/topologies/mongos.js +10 -3
- package/lib/core/topologies/replset.js +49 -11
- package/lib/core/topologies/server.js +38 -3
- package/lib/core/topologies/shared.js +22 -0
- package/lib/core/transactions.js +1 -0
- package/lib/core/utils.js +45 -1
- package/lib/core/wireprotocol/command.js +2 -2
- package/lib/core/wireprotocol/get_more.js +4 -0
- package/lib/core/wireprotocol/query.js +8 -1
- package/lib/cursor.js +780 -840
- package/lib/db.js +31 -73
- package/lib/error.js +10 -8
- package/lib/gridfs-stream/download.js +12 -5
- package/lib/mongo_client.js +33 -21
- package/lib/operations/aggregate.js +77 -95
- package/lib/operations/close.js +46 -0
- package/lib/operations/collection_ops.js +7 -916
- package/lib/operations/command.js +2 -1
- package/lib/operations/command_v2.js +109 -0
- package/lib/operations/common_functions.js +48 -14
- package/lib/operations/connect.js +73 -9
- package/lib/operations/count.js +8 -8
- package/lib/operations/count_documents.js +24 -29
- package/lib/operations/create_collection.js +1 -0
- package/lib/operations/cursor_ops.js +22 -32
- package/lib/operations/db_ops.js +0 -178
- package/lib/operations/distinct.js +20 -21
- package/lib/operations/drop.js +1 -0
- package/lib/operations/estimated_document_count.js +43 -18
- package/lib/operations/execute_operation.js +115 -3
- package/lib/operations/explain.js +2 -6
- package/lib/operations/find.js +35 -0
- package/lib/operations/insert_one.js +1 -37
- package/lib/operations/list_collections.js +106 -0
- package/lib/operations/list_databases.js +38 -0
- package/lib/operations/list_indexes.js +28 -52
- package/lib/operations/operation.js +7 -1
- package/lib/operations/rename.js +1 -1
- package/lib/operations/to_array.js +3 -5
- package/lib/read_concern.js +7 -1
- package/lib/topologies/mongos.js +1 -1
- package/lib/topologies/replset.js +1 -1
- package/lib/topologies/server.js +2 -2
- package/lib/topologies/topology_base.js +4 -5
- package/lib/utils.js +4 -4
- package/package.json +1 -1
- package/lib/operations/aggregate_operation.js +0 -127
- package/lib/operations/mongo_client_ops.js +0 -731
package/lib/core/error.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const mongoErrorContextSymbol = Symbol('mongoErrorContextSymbol');
|
|
4
|
+
const maxWireVersion = require('./utils').maxWireVersion;
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Creates a new MongoError
|
|
@@ -152,6 +153,73 @@ function isRetryableError(error) {
|
|
|
152
153
|
);
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
const SDAM_RECOVERING_CODES = new Set([
|
|
157
|
+
91, // ShutdownInProgress
|
|
158
|
+
189, // PrimarySteppedDown
|
|
159
|
+
11600, // InterruptedAtShutdown
|
|
160
|
+
11602, // InterruptedDueToReplStateChange
|
|
161
|
+
13436 // NotMasterOrSecondary
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
const SDAM_NOTMASTER_CODES = new Set([
|
|
165
|
+
10107, // NotMaster
|
|
166
|
+
13435 // NotMasterNoSlaveOk
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
|
|
170
|
+
11600, // InterruptedAtShutdown
|
|
171
|
+
91 // ShutdownInProgress
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
function isRecoveringError(err) {
|
|
175
|
+
if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isNotMasterError(err) {
|
|
183
|
+
if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (isRecoveringError(err)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return err.message.match(/not master/);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isNodeShuttingDownError(err) {
|
|
195
|
+
return err.code && SDAM_NODE_SHUTTING_DOWN_ERROR_CODES.has(err.code);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Determines whether SDAM can recover from a given error. If it cannot
|
|
200
|
+
* then the pool will be cleared, and server state will completely reset
|
|
201
|
+
* locally.
|
|
202
|
+
*
|
|
203
|
+
* @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
|
|
204
|
+
* @param {MongoError|Error} error
|
|
205
|
+
* @param {Server} server
|
|
206
|
+
*/
|
|
207
|
+
function isSDAMUnrecoverableError(error, server) {
|
|
208
|
+
if (error instanceof MongoParseError) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (isRecoveringError(error) || isNotMasterError(error)) {
|
|
213
|
+
if (maxWireVersion(server) >= 8 && !isNodeShuttingDownError(error)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
155
223
|
module.exports = {
|
|
156
224
|
MongoError,
|
|
157
225
|
MongoNetworkError,
|
|
@@ -159,5 +227,6 @@ module.exports = {
|
|
|
159
227
|
MongoTimeoutError,
|
|
160
228
|
MongoWriteConcernError,
|
|
161
229
|
mongoErrorContextSymbol,
|
|
162
|
-
isRetryableError
|
|
230
|
+
isRetryableError,
|
|
231
|
+
isSDAMUnrecoverableError
|
|
163
232
|
};
|
package/lib/core/index.js
CHANGED
|
@@ -28,7 +28,7 @@ module.exports = {
|
|
|
28
28
|
ReplSet: require('./topologies/replset'),
|
|
29
29
|
Mongos: require('./topologies/mongos'),
|
|
30
30
|
Logger: require('./connection/logger'),
|
|
31
|
-
Cursor: require('./cursor'),
|
|
31
|
+
Cursor: require('./cursor').CoreCursor,
|
|
32
32
|
ReadPreference: require('./topologies/read_preference'),
|
|
33
33
|
Sessions: require('./sessions'),
|
|
34
34
|
BSON: BSON,
|
package/lib/core/sdam/server.js
CHANGED
|
@@ -14,6 +14,7 @@ const MongoParseError = require('../error').MongoParseError;
|
|
|
14
14
|
const MongoNetworkError = require('../error').MongoNetworkError;
|
|
15
15
|
const collationNotSupported = require('../utils').collationNotSupported;
|
|
16
16
|
const debugOptions = require('../connection/utils').debugOptions;
|
|
17
|
+
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
|
|
17
18
|
|
|
18
19
|
// Used for filtering out fields for logging
|
|
19
20
|
const DEBUG_FIELDS = [
|
|
@@ -95,6 +96,13 @@ class Server extends EventEmitter {
|
|
|
95
96
|
return this.s.description.address;
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
get autoEncrypter() {
|
|
100
|
+
if (this.s.options && this.s.options.autoEncrypter) {
|
|
101
|
+
return this.s.options.autoEncrypter;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
98
106
|
/**
|
|
99
107
|
* Initiate server connect
|
|
100
108
|
*/
|
|
@@ -154,13 +162,16 @@ class Server extends EventEmitter {
|
|
|
154
162
|
if (typeof options === 'function') (callback = options), (options = {});
|
|
155
163
|
options = Object.assign({}, { force: false }, options);
|
|
156
164
|
|
|
157
|
-
|
|
165
|
+
const done = err => {
|
|
166
|
+
this.emit('closed');
|
|
158
167
|
this.s.state = STATE_DISCONNECTED;
|
|
159
168
|
if (typeof callback === 'function') {
|
|
160
|
-
callback(
|
|
169
|
+
callback(err, null);
|
|
161
170
|
}
|
|
171
|
+
};
|
|
162
172
|
|
|
163
|
-
|
|
173
|
+
if (!this.s.pool) {
|
|
174
|
+
return done();
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
['close', 'error', 'timeout', 'parseError', 'connect'].forEach(event => {
|
|
@@ -171,10 +182,7 @@ class Server extends EventEmitter {
|
|
|
171
182
|
clearTimeout(this.s.monitorId);
|
|
172
183
|
}
|
|
173
184
|
|
|
174
|
-
this.s.pool.destroy(options.force,
|
|
175
|
-
this.s.state = STATE_DISCONNECTED;
|
|
176
|
-
callback(err);
|
|
177
|
-
});
|
|
185
|
+
this.s.pool.destroy(options.force, done);
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
/**
|
|
@@ -231,7 +239,86 @@ class Server extends EventEmitter {
|
|
|
231
239
|
return;
|
|
232
240
|
}
|
|
233
241
|
|
|
234
|
-
wireProtocol.command(this, ns, cmd, options,
|
|
242
|
+
wireProtocol.command(this, ns, cmd, options, (err, result) => {
|
|
243
|
+
if (err) {
|
|
244
|
+
if (options.session && err instanceof MongoNetworkError) {
|
|
245
|
+
options.session.serverSession.isDirty = true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (isSDAMUnrecoverableError(err, this)) {
|
|
249
|
+
this.emit('error', err);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
callback(err, result);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Execute a query against the server
|
|
259
|
+
*
|
|
260
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
261
|
+
* @param {object} cmd The command document for the query
|
|
262
|
+
* @param {object} options Optional settings
|
|
263
|
+
* @param {function} callback
|
|
264
|
+
*/
|
|
265
|
+
query(ns, cmd, cursorState, options, callback) {
|
|
266
|
+
wireProtocol.query(this, ns, cmd, cursorState, options, (err, result) => {
|
|
267
|
+
if (err) {
|
|
268
|
+
if (options.session && err instanceof MongoNetworkError) {
|
|
269
|
+
options.session.serverSession.isDirty = true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (isSDAMUnrecoverableError(err, this)) {
|
|
273
|
+
this.emit('error', err);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
callback(err, result);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Execute a `getMore` against the server
|
|
283
|
+
*
|
|
284
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
285
|
+
* @param {object} cursorState State data associated with the cursor calling this method
|
|
286
|
+
* @param {object} options Optional settings
|
|
287
|
+
* @param {function} callback
|
|
288
|
+
*/
|
|
289
|
+
getMore(ns, cursorState, batchSize, options, callback) {
|
|
290
|
+
wireProtocol.getMore(this, ns, cursorState, batchSize, options, (err, result) => {
|
|
291
|
+
if (err) {
|
|
292
|
+
if (options.session && err instanceof MongoNetworkError) {
|
|
293
|
+
options.session.serverSession.isDirty = true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (isSDAMUnrecoverableError(err, this)) {
|
|
297
|
+
this.emit('error', err);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
callback(err, result);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Execute a `killCursors` command against the server
|
|
307
|
+
*
|
|
308
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
309
|
+
* @param {object} cursorState State data associated with the cursor calling this method
|
|
310
|
+
* @param {function} callback
|
|
311
|
+
*/
|
|
312
|
+
killCursors(ns, cursorState, callback) {
|
|
313
|
+
wireProtocol.killCursors(this, ns, cursorState, (err, result) => {
|
|
314
|
+
if (err && isSDAMUnrecoverableError(err, this)) {
|
|
315
|
+
this.emit('error', err);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (typeof callback === 'function') {
|
|
319
|
+
callback(err, result);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
235
322
|
}
|
|
236
323
|
|
|
237
324
|
/**
|
|
@@ -332,11 +419,23 @@ function executeWriteOperation(args, options, callback) {
|
|
|
332
419
|
}
|
|
333
420
|
|
|
334
421
|
if (collationNotSupported(server, options)) {
|
|
335
|
-
callback(new MongoError(`server ${
|
|
422
|
+
callback(new MongoError(`server ${server.name} does not support collation`));
|
|
336
423
|
return;
|
|
337
424
|
}
|
|
338
425
|
|
|
339
|
-
return wireProtocol[op](server, ns, ops, options,
|
|
426
|
+
return wireProtocol[op](server, ns, ops, options, (err, result) => {
|
|
427
|
+
if (err) {
|
|
428
|
+
if (options.session && err instanceof MongoNetworkError) {
|
|
429
|
+
options.session.serverSession.isDirty = true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (isSDAMUnrecoverableError(err, server)) {
|
|
433
|
+
server.emit('error', err);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
callback(err, result);
|
|
438
|
+
});
|
|
340
439
|
}
|
|
341
440
|
|
|
342
441
|
function connectEventHandler(server) {
|
|
@@ -19,6 +19,13 @@ const WRITABLE_SERVER_TYPES = new Set([
|
|
|
19
19
|
ServerType.Mongos
|
|
20
20
|
]);
|
|
21
21
|
|
|
22
|
+
const DATA_BEARING_SERVER_TYPES = new Set([
|
|
23
|
+
ServerType.RSPrimary,
|
|
24
|
+
ServerType.RSSecondary,
|
|
25
|
+
ServerType.Mongos,
|
|
26
|
+
ServerType.Standalone
|
|
27
|
+
]);
|
|
28
|
+
|
|
22
29
|
const ISMASTER_FIELDS = [
|
|
23
30
|
'minWireVersion',
|
|
24
31
|
'maxWireVersion',
|
|
@@ -99,6 +106,13 @@ class ServerDescription {
|
|
|
99
106
|
return this.type === ServerType.RSSecondary || this.isWritable;
|
|
100
107
|
}
|
|
101
108
|
|
|
109
|
+
/**
|
|
110
|
+
* @return {Boolean} Is this server data bearing
|
|
111
|
+
*/
|
|
112
|
+
get isDataBearing() {
|
|
113
|
+
return DATA_BEARING_SERVER_TYPES.has(this.type);
|
|
114
|
+
}
|
|
115
|
+
|
|
102
116
|
/**
|
|
103
117
|
* @return {Boolean} Is this server available for writes
|
|
104
118
|
*/
|
|
@@ -13,17 +13,18 @@ const ReadPreference = require('../topologies/read_preference');
|
|
|
13
13
|
const readPreferenceServerSelector = require('./server_selectors').readPreferenceServerSelector;
|
|
14
14
|
const writableServerSelector = require('./server_selectors').writableServerSelector;
|
|
15
15
|
const isRetryableWritesSupported = require('../topologies/shared').isRetryableWritesSupported;
|
|
16
|
-
const
|
|
16
|
+
const CoreCursor = require('../cursor').CoreCursor;
|
|
17
17
|
const deprecate = require('util').deprecate;
|
|
18
18
|
const BSON = require('../connection/utils').retrieveBSON();
|
|
19
19
|
const createCompressionInfo = require('../topologies/shared').createCompressionInfo;
|
|
20
20
|
const isRetryableError = require('../error').isRetryableError;
|
|
21
|
-
const
|
|
21
|
+
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
|
|
22
22
|
const ClientSession = require('../sessions').ClientSession;
|
|
23
23
|
const createClientInfo = require('../topologies/shared').createClientInfo;
|
|
24
24
|
const MongoError = require('../error').MongoError;
|
|
25
25
|
const resolveClusterTime = require('../topologies/shared').resolveClusterTime;
|
|
26
26
|
const SrvPoller = require('./srv_polling').SrvPoller;
|
|
27
|
+
const getMMAPError = require('../topologies/shared').getMMAPError;
|
|
27
28
|
|
|
28
29
|
// Global state
|
|
29
30
|
let globalTopologyCounter = 0;
|
|
@@ -31,8 +32,8 @@ let globalTopologyCounter = 0;
|
|
|
31
32
|
// Constants
|
|
32
33
|
const TOPOLOGY_DEFAULTS = {
|
|
33
34
|
localThresholdMS: 15,
|
|
34
|
-
serverSelectionTimeoutMS:
|
|
35
|
-
heartbeatFrequencyMS:
|
|
35
|
+
serverSelectionTimeoutMS: 30000,
|
|
36
|
+
heartbeatFrequencyMS: 10000,
|
|
36
37
|
minHeartbeatFrequencyMS: 500
|
|
37
38
|
};
|
|
38
39
|
|
|
@@ -130,7 +131,7 @@ class Topology extends EventEmitter {
|
|
|
130
131
|
heartbeatFrequencyMS: options.heartbeatFrequencyMS,
|
|
131
132
|
minHeartbeatIntervalMS: options.minHeartbeatIntervalMS,
|
|
132
133
|
// allow users to override the cursor factory
|
|
133
|
-
Cursor: options.cursorFactory ||
|
|
134
|
+
Cursor: options.cursorFactory || CoreCursor,
|
|
134
135
|
// the bson parser
|
|
135
136
|
bson: options.bson || new BSON(),
|
|
136
137
|
// a map of server instances to normalized addresses
|
|
@@ -138,7 +139,7 @@ class Topology extends EventEmitter {
|
|
|
138
139
|
// Server Session Pool
|
|
139
140
|
sessionPool: null,
|
|
140
141
|
// Active client sessions
|
|
141
|
-
sessions:
|
|
142
|
+
sessions: new Set(),
|
|
142
143
|
// Promise library
|
|
143
144
|
promiseLibrary: options.promiseLibrary || Promise,
|
|
144
145
|
credentials: options.credentials,
|
|
@@ -344,8 +345,14 @@ class Topology extends EventEmitter {
|
|
|
344
345
|
if (typeof selector !== 'function') {
|
|
345
346
|
options = selector;
|
|
346
347
|
|
|
347
|
-
|
|
348
|
-
|
|
348
|
+
let readPreference;
|
|
349
|
+
if (selector instanceof ReadPreference) {
|
|
350
|
+
readPreference = selector;
|
|
351
|
+
} else {
|
|
352
|
+
translateReadPreference(options);
|
|
353
|
+
readPreference = options.readPreference || ReadPreference.primary;
|
|
354
|
+
}
|
|
355
|
+
|
|
349
356
|
selector = readPreferenceServerSelector(readPreference);
|
|
350
357
|
} else {
|
|
351
358
|
options = {};
|
|
@@ -390,6 +397,17 @@ class Topology extends EventEmitter {
|
|
|
390
397
|
}
|
|
391
398
|
|
|
392
399
|
// Sessions related methods
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* @return Whether the topology should initiate selection to determine session support
|
|
403
|
+
*/
|
|
404
|
+
shouldCheckForSessionSupport() {
|
|
405
|
+
return (
|
|
406
|
+
(this.description.type === TopologyType.Single && !this.description.hasKnownServers) ||
|
|
407
|
+
!this.description.hasDataBearingServers
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
393
411
|
/**
|
|
394
412
|
* @return Whether sessions are supported on the current topology
|
|
395
413
|
*/
|
|
@@ -403,10 +421,10 @@ class Topology extends EventEmitter {
|
|
|
403
421
|
startSession(options, clientOptions) {
|
|
404
422
|
const session = new ClientSession(this, this.s.sessionPool, options, clientOptions);
|
|
405
423
|
session.once('ended', () => {
|
|
406
|
-
this.s.sessions
|
|
424
|
+
this.s.sessions.delete(session);
|
|
407
425
|
});
|
|
408
426
|
|
|
409
|
-
this.s.sessions.
|
|
427
|
+
this.s.sessions.add(session);
|
|
410
428
|
return session;
|
|
411
429
|
}
|
|
412
430
|
|
|
@@ -632,7 +650,7 @@ class Topology extends EventEmitter {
|
|
|
632
650
|
const CursorClass = options.cursorFactory || this.s.Cursor;
|
|
633
651
|
translateReadPreference(options);
|
|
634
652
|
|
|
635
|
-
return new CursorClass(
|
|
653
|
+
return new CursorClass(topology, ns, cmd, options);
|
|
636
654
|
}
|
|
637
655
|
|
|
638
656
|
get clientInfo() {
|
|
@@ -919,7 +937,7 @@ function serverErrorEventHandler(server, topology) {
|
|
|
919
937
|
new monitoring.ServerClosedEvent(topology.s.id, server.description.address)
|
|
920
938
|
);
|
|
921
939
|
|
|
922
|
-
if (err
|
|
940
|
+
if (isSDAMUnrecoverableError(err, server)) {
|
|
923
941
|
resetServerState(server, err, { clearPool: true });
|
|
924
942
|
return;
|
|
925
943
|
}
|
|
@@ -954,6 +972,7 @@ function executeWriteOperation(args, options, callback) {
|
|
|
954
972
|
const handler = (err, result) => {
|
|
955
973
|
if (!err) return callback(null, result);
|
|
956
974
|
if (!isRetryableError(err)) {
|
|
975
|
+
err = getMMAPError(err);
|
|
957
976
|
return callback(err);
|
|
958
977
|
}
|
|
959
978
|
|
|
@@ -997,10 +1016,11 @@ function resetServerState(server, error, options) {
|
|
|
997
1016
|
'descriptionReceived',
|
|
998
1017
|
new ServerDescription(server.description.address, null, { error })
|
|
999
1018
|
);
|
|
1019
|
+
server.monitor();
|
|
1000
1020
|
}
|
|
1001
1021
|
|
|
1002
|
-
if (options.clearPool && server.pool) {
|
|
1003
|
-
server.pool.reset(() => resetState());
|
|
1022
|
+
if (options.clearPool && server.s.pool) {
|
|
1023
|
+
server.s.pool.reset(() => resetState());
|
|
1004
1024
|
return;
|
|
1005
1025
|
}
|
|
1006
1026
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const ServerType = require('./server_description').ServerType;
|
|
3
3
|
const ServerDescription = require('./server_description').ServerDescription;
|
|
4
|
-
const ReadPreference = require('../topologies/read_preference');
|
|
5
4
|
const WIRE_CONSTANTS = require('../wireprotocol/constants');
|
|
6
5
|
|
|
7
6
|
// contstants related to compatability checks
|
|
@@ -258,24 +257,17 @@ class TopologyDescription {
|
|
|
258
257
|
}
|
|
259
258
|
|
|
260
259
|
/**
|
|
261
|
-
* Determines if the topology has
|
|
262
|
-
* following section for behaviour rules.
|
|
263
|
-
*
|
|
264
|
-
* @param {ReadPreference} [readPreference] An optional read preference for determining if a readable server is present
|
|
265
|
-
* @return {Boolean} Whether there is a readable server in this topology
|
|
260
|
+
* Determines if the topology description has any known servers
|
|
266
261
|
*/
|
|
267
|
-
|
|
268
|
-
|
|
262
|
+
get hasKnownServers() {
|
|
263
|
+
return Array.from(this.servers.values()).some(sd => sd.type !== ServerDescription.Unknown);
|
|
269
264
|
}
|
|
270
265
|
|
|
271
266
|
/**
|
|
272
|
-
* Determines if
|
|
273
|
-
* following section for behaviour rules.
|
|
274
|
-
*
|
|
275
|
-
* @return {Boolean} Whether there is a writable server in this topology
|
|
267
|
+
* Determines if this topology description has a data-bearing server available.
|
|
276
268
|
*/
|
|
277
|
-
|
|
278
|
-
return this.
|
|
269
|
+
get hasDataBearingServers() {
|
|
270
|
+
return Array.from(this.servers.values()).some(sd => sd.isDataBearing);
|
|
279
271
|
}
|
|
280
272
|
|
|
281
273
|
/**
|
package/lib/core/sessions.js
CHANGED
|
@@ -18,7 +18,7 @@ const resolveClusterTime = require('./topologies/shared').resolveClusterTime;
|
|
|
18
18
|
const isSharded = require('./wireprotocol/shared').isSharded;
|
|
19
19
|
const maxWireVersion = require('./utils').maxWireVersion;
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const minWireVersionForShardedTransactions = 8;
|
|
22
22
|
|
|
23
23
|
function assertAlive(session, callback) {
|
|
24
24
|
if (session.serverSession == null) {
|
|
@@ -191,8 +191,9 @@ class ClientSession extends EventEmitter {
|
|
|
191
191
|
|
|
192
192
|
const topologyMaxWireVersion = maxWireVersion(this.topology);
|
|
193
193
|
if (
|
|
194
|
-
isSharded(this.topology)
|
|
195
|
-
|
|
194
|
+
isSharded(this.topology) &&
|
|
195
|
+
topologyMaxWireVersion != null &&
|
|
196
|
+
topologyMaxWireVersion < minWireVersionForShardedTransactions
|
|
196
197
|
) {
|
|
197
198
|
throw new MongoError('Transactions are not supported on sharded clusters in MongoDB < 4.2.');
|
|
198
199
|
}
|
|
@@ -286,6 +287,7 @@ class ClientSession extends EventEmitter {
|
|
|
286
287
|
const MAX_WITH_TRANSACTION_TIMEOUT = 120000;
|
|
287
288
|
const UNSATISFIABLE_WRITE_CONCERN_CODE = 100;
|
|
288
289
|
const UNKNOWN_REPL_WRITE_CONCERN_CODE = 79;
|
|
290
|
+
const MAX_TIME_MS_EXPIRED_CODE = 50;
|
|
289
291
|
const NON_DETERMINISTIC_WRITE_CONCERN_ERRORS = new Set([
|
|
290
292
|
'CannotSatisfyWriteConcern',
|
|
291
293
|
'UnknownReplWriteConcern',
|
|
@@ -298,15 +300,27 @@ function hasNotTimedOut(startTime, max) {
|
|
|
298
300
|
|
|
299
301
|
function isUnknownTransactionCommitResult(err) {
|
|
300
302
|
return (
|
|
301
|
-
|
|
302
|
-
err.
|
|
303
|
-
|
|
303
|
+
isMaxTimeMSExpiredError(err) ||
|
|
304
|
+
(!NON_DETERMINISTIC_WRITE_CONCERN_ERRORS.has(err.codeName) &&
|
|
305
|
+
err.code !== UNSATISFIABLE_WRITE_CONCERN_CODE &&
|
|
306
|
+
err.code !== UNKNOWN_REPL_WRITE_CONCERN_CODE)
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function isMaxTimeMSExpiredError(err) {
|
|
311
|
+
return (
|
|
312
|
+
err.code === MAX_TIME_MS_EXPIRED_CODE ||
|
|
313
|
+
(err.writeConcernError && err.writeConcernError.code === MAX_TIME_MS_EXPIRED_CODE)
|
|
304
314
|
);
|
|
305
315
|
}
|
|
306
316
|
|
|
307
317
|
function attemptTransactionCommit(session, startTime, fn, options) {
|
|
308
318
|
return session.commitTransaction().catch(err => {
|
|
309
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
err instanceof MongoError &&
|
|
321
|
+
hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT) &&
|
|
322
|
+
!isMaxTimeMSExpiredError(err)
|
|
323
|
+
) {
|
|
310
324
|
if (err.hasErrorLabel('UnknownTransactionCommitResult')) {
|
|
311
325
|
return attemptTransactionCommit(session, startTime, fn, options);
|
|
312
326
|
}
|
|
@@ -363,6 +377,13 @@ function attemptTransaction(session, startTime, fn, options) {
|
|
|
363
377
|
return attemptTransaction(session, startTime, fn, options);
|
|
364
378
|
}
|
|
365
379
|
|
|
380
|
+
if (isMaxTimeMSExpiredError(err)) {
|
|
381
|
+
if (err.errorLabels == null) {
|
|
382
|
+
err.errorLabels = [];
|
|
383
|
+
}
|
|
384
|
+
err.errorLabels.push('UnknownTransactionCommitResult');
|
|
385
|
+
}
|
|
386
|
+
|
|
366
387
|
throw err;
|
|
367
388
|
}
|
|
368
389
|
|
|
@@ -444,6 +465,10 @@ function endTransaction(session, commandName, callback) {
|
|
|
444
465
|
Object.assign(command, { writeConcern });
|
|
445
466
|
}
|
|
446
467
|
|
|
468
|
+
if (commandName === 'commitTransaction' && session.transaction.options.maxTimeMS) {
|
|
469
|
+
Object.assign(command, { maxTimeMS: session.transaction.options.maxTimeMS });
|
|
470
|
+
}
|
|
471
|
+
|
|
447
472
|
function commandHandler(e, r) {
|
|
448
473
|
if (commandName === 'commitTransaction') {
|
|
449
474
|
session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
|
|
@@ -452,7 +477,8 @@ function endTransaction(session, commandName, callback) {
|
|
|
452
477
|
e &&
|
|
453
478
|
(e instanceof MongoNetworkError ||
|
|
454
479
|
e instanceof MongoWriteConcernError ||
|
|
455
|
-
isRetryableError(e)
|
|
480
|
+
isRetryableError(e) ||
|
|
481
|
+
isMaxTimeMSExpiredError(e))
|
|
456
482
|
) {
|
|
457
483
|
if (e.errorLabels) {
|
|
458
484
|
const idx = e.errorLabels.indexOf('TransientTransactionError');
|
|
@@ -527,6 +553,7 @@ class ServerSession {
|
|
|
527
553
|
this.id = { id: new Binary(uuidV4(), Binary.SUBTYPE_UUID) };
|
|
528
554
|
this.lastUse = Date.now();
|
|
529
555
|
this.txnNumber = 0;
|
|
556
|
+
this.isDirty = false;
|
|
530
557
|
}
|
|
531
558
|
|
|
532
559
|
/**
|
|
@@ -603,8 +630,8 @@ class ServerSessionPool {
|
|
|
603
630
|
release(session) {
|
|
604
631
|
const sessionTimeoutMinutes = this.topology.logicalSessionTimeoutMinutes;
|
|
605
632
|
while (this.sessions.length) {
|
|
606
|
-
const
|
|
607
|
-
if (
|
|
633
|
+
const pooledSession = this.sessions[this.sessions.length - 1];
|
|
634
|
+
if (pooledSession.hasTimedOut(sessionTimeoutMinutes)) {
|
|
608
635
|
this.sessions.pop();
|
|
609
636
|
} else {
|
|
610
637
|
break;
|
|
@@ -612,11 +639,38 @@ class ServerSessionPool {
|
|
|
612
639
|
}
|
|
613
640
|
|
|
614
641
|
if (!session.hasTimedOut(sessionTimeoutMinutes)) {
|
|
642
|
+
if (session.isDirty) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// otherwise, readd this session to the session pool
|
|
615
647
|
this.sessions.unshift(session);
|
|
616
648
|
}
|
|
617
649
|
}
|
|
618
650
|
}
|
|
619
651
|
|
|
652
|
+
// TODO: this should be codified in command construction
|
|
653
|
+
// @see https://github.com/mongodb/specifications/blob/master/source/read-write-concern/read-write-concern.rst#read-concern
|
|
654
|
+
function commandSupportsReadConcern(command, options) {
|
|
655
|
+
if (
|
|
656
|
+
command.aggregate ||
|
|
657
|
+
command.count ||
|
|
658
|
+
command.distinct ||
|
|
659
|
+
command.find ||
|
|
660
|
+
command.parallelCollectionScan ||
|
|
661
|
+
command.geoNear ||
|
|
662
|
+
command.geoSearch
|
|
663
|
+
) {
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (command.mapReduce && options.out && (options.out.inline === 1 || options.out === 'inline')) {
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
|
|
620
674
|
/**
|
|
621
675
|
* Optionally decorate a command with sessions specific keys
|
|
622
676
|
*
|
|
@@ -640,6 +694,7 @@ function applySession(session, command, options) {
|
|
|
640
694
|
// first apply non-transaction-specific sessions data
|
|
641
695
|
const inTransaction = session.inTransaction() || isTransactionCommand(command);
|
|
642
696
|
const isRetryableWrite = options.willRetryWrite;
|
|
697
|
+
const shouldApplyReadConcern = commandSupportsReadConcern(command);
|
|
643
698
|
|
|
644
699
|
if (serverSession.txnNumber && (isRetryableWrite || inTransaction)) {
|
|
645
700
|
command.txnNumber = BSON.Long.fromNumber(serverSession.txnNumber);
|
|
@@ -653,7 +708,7 @@ function applySession(session, command, options) {
|
|
|
653
708
|
|
|
654
709
|
// TODO: the following should only be applied to read operation per spec.
|
|
655
710
|
// for causal consistency
|
|
656
|
-
if (session.supports.causalConsistency && session.operationTime) {
|
|
711
|
+
if (session.supports.causalConsistency && session.operationTime && shouldApplyReadConcern) {
|
|
657
712
|
command.readConcern = command.readConcern || {};
|
|
658
713
|
Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
|
|
659
714
|
}
|
|
@@ -707,5 +762,6 @@ module.exports = {
|
|
|
707
762
|
ServerSessionPool,
|
|
708
763
|
TxnState,
|
|
709
764
|
applySession,
|
|
710
|
-
updateSessionFromResponse
|
|
765
|
+
updateSessionFromResponse,
|
|
766
|
+
commandSupportsReadConcern
|
|
711
767
|
};
|