mongodb 3.5.4 → 3.5.8
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 +58 -0
- package/README.md +1 -1
- package/lib/bulk/common.js +4 -11
- package/lib/bulk/unordered.js +8 -0
- package/lib/change_stream.js +181 -146
- package/lib/cmap/connection_pool.js +11 -3
- package/lib/collection.js +1 -1
- package/lib/core/cursor.js +16 -29
- package/lib/core/error.js +11 -4
- package/lib/core/index.js +0 -1
- package/lib/core/sdam/server.js +4 -2
- package/lib/core/sdam/server_selection.js +22 -36
- package/lib/core/sdam/topology.js +29 -23
- package/lib/core/sessions.js +18 -8
- package/lib/core/topologies/shared.js +9 -2
- package/lib/core/uri_parser.js +3 -3
- package/lib/core/utils.js +13 -11
- package/lib/cursor.js +20 -43
- package/lib/error.js +26 -33
- package/lib/mongo_client.js +16 -11
- package/lib/operations/connect.js +6 -0
- package/lib/operations/cursor_ops.js +2 -3
- package/lib/operations/db_ops.js +0 -361
- package/lib/utils.js +8 -3
- package/package.json +6 -6
package/HISTORY.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
<a name="3.5.8"></a>
|
|
6
|
+
## [3.5.8](https://github.com/mongodb/node-mongodb-native/compare/v3.5.7...v3.5.8) (2020-05-28)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* always clear cancelled wait queue members during processing ([0394f9d](https://github.com/mongodb/node-mongodb-native/commit/0394f9d))
|
|
12
|
+
* always include `writeErrors` on a `BulkWriteError` instance ([58b4f94](https://github.com/mongodb/node-mongodb-native/commit/58b4f94))
|
|
13
|
+
* ensure implicit sessions are ended consistently ([5c6fda1](https://github.com/mongodb/node-mongodb-native/commit/5c6fda1))
|
|
14
|
+
* filter servers before applying reducers ([4faf9f5](https://github.com/mongodb/node-mongodb-native/commit/4faf9f5))
|
|
15
|
+
* unordered bulk write should attempt to execute all batches ([6cee96b](https://github.com/mongodb/node-mongodb-native/commit/6cee96b))
|
|
16
|
+
* **ChangeStream:** should resume from errors when iterating ([5ecf18e](https://github.com/mongodb/node-mongodb-native/commit/5ecf18e))
|
|
17
|
+
* honor journal=true in connection string ([#2359](https://github.com/mongodb/node-mongodb-native/issues/2359)) ([246669f](https://github.com/mongodb/node-mongodb-native/commit/246669f))
|
|
18
|
+
* **ChangeStream:** whitelist resumable errors ([#2337](https://github.com/mongodb/node-mongodb-native/issues/2337)) ([a9d3965](https://github.com/mongodb/node-mongodb-native/commit/a9d3965)), closes [#17](https://github.com/mongodb/node-mongodb-native/issues/17) [#18](https://github.com/mongodb/node-mongodb-native/issues/18)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
<a name="3.5.7"></a>
|
|
23
|
+
## [3.5.7](https://github.com/mongodb/node-mongodb-native/compare/v3.5.6...v3.5.7) (2020-04-29)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* limit growth of server sessions through lazy acquisition ([3d05a6d](https://github.com/mongodb/node-mongodb-native/commit/3d05a6d))
|
|
29
|
+
* remove circular dependency warnings on node 14 ([56a1b8a](https://github.com/mongodb/node-mongodb-native/commit/56a1b8a))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
<a name="3.5.6"></a>
|
|
34
|
+
## [3.5.6](https://github.com/mongodb/node-mongodb-native/compare/v3.5.5...v3.5.6) (2020-04-14)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
* always return empty array for selection on unknown topology ([f9e786a](https://github.com/mongodb/node-mongodb-native/commit/f9e786a))
|
|
40
|
+
* createCollection only uses listCollections in strict mode ([d368f12](https://github.com/mongodb/node-mongodb-native/commit/d368f12))
|
|
41
|
+
* don't throw if `withTransaction()` callback rejects with a null reason ([153646c](https://github.com/mongodb/node-mongodb-native/commit/153646c))
|
|
42
|
+
* only mark server session dirty if the client session is alive ([611be8d](https://github.com/mongodb/node-mongodb-native/commit/611be8d))
|
|
43
|
+
* polyfill for util.promisify ([1c4cf6c](https://github.com/mongodb/node-mongodb-native/commit/1c4cf6c))
|
|
44
|
+
* single `readPreferenceTags` should be parsed as an array ([a50611b](https://github.com/mongodb/node-mongodb-native/commit/a50611b))
|
|
45
|
+
* **cursor:** transforms should only be applied once to documents ([704f30a](https://github.com/mongodb/node-mongodb-native/commit/704f30a))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
<a name="3.5.5"></a>
|
|
50
|
+
## [3.5.5](https://github.com/mongodb/node-mongodb-native/compare/v3.5.4...v3.5.5) (2020-03-11)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Bug Fixes
|
|
54
|
+
|
|
55
|
+
* correctly use template string for connection string error message ([6238c84](https://github.com/mongodb/node-mongodb-native/commit/6238c84))
|
|
56
|
+
* don't depend on private node api for `Timeout` wrapper ([3ddaa3e](https://github.com/mongodb/node-mongodb-native/commit/3ddaa3e))
|
|
57
|
+
* multiple concurrent attempts to process the queue may fail ([f69f51c](https://github.com/mongodb/node-mongodb-native/commit/f69f51c))
|
|
58
|
+
* pass optional promise lib to maybePromise ([cde11ec](https://github.com/mongodb/node-mongodb-native/commit/cde11ec))
|
|
59
|
+
* **cursor:** hasNext consumes documents on cursor with limit ([ef04d00](https://github.com/mongodb/node-mongodb-native/commit/ef04d00))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
5
63
|
<a name="3.5.4"></a>
|
|
6
64
|
## [3.5.4](https://github.com/mongodb/node-mongodb-native/compare/v3.5.3...v3.5.4) (2020-02-25)
|
|
7
65
|
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Core Server (i.e. SERVER) project are **public**.
|
|
|
33
33
|
|
|
34
34
|
### Support / Feedback
|
|
35
35
|
|
|
36
|
-
For issues with, questions about, or feedback for the Node.js driver, please look into our [support channels](
|
|
36
|
+
For issues with, questions about, or feedback for the Node.js driver, please look into our [support channels](https://docs.mongodb.com/manual/support). Please do not email any of the driver developers directly with issues or questions - you're more likely to get an answer on the [MongoDB Community Forums](https://community.mongodb.com/tags/c/drivers-odms-connectors/7/node-js-driver).
|
|
37
37
|
|
|
38
38
|
### Change Log
|
|
39
39
|
|
package/lib/bulk/common.js
CHANGED
|
@@ -1200,19 +1200,10 @@ class BulkOperationBase {
|
|
|
1200
1200
|
* @ignore
|
|
1201
1201
|
* @param {function} callback
|
|
1202
1202
|
* @param {BulkWriteResult} writeResult
|
|
1203
|
-
* @param {class} self either OrderedBulkOperation or
|
|
1203
|
+
* @param {class} self either OrderedBulkOperation or UnorderedBulkOperation
|
|
1204
1204
|
*/
|
|
1205
1205
|
handleWriteError(callback, writeResult) {
|
|
1206
1206
|
if (this.s.bulkResult.writeErrors.length > 0) {
|
|
1207
|
-
if (this.s.bulkResult.writeErrors.length === 1) {
|
|
1208
|
-
handleCallback(
|
|
1209
|
-
callback,
|
|
1210
|
-
new BulkWriteError(toError(this.s.bulkResult.writeErrors[0]), writeResult),
|
|
1211
|
-
null
|
|
1212
|
-
);
|
|
1213
|
-
return true;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
1207
|
const msg = this.s.bulkResult.writeErrors[0].errmsg
|
|
1217
1208
|
? this.s.bulkResult.writeErrors[0].errmsg
|
|
1218
1209
|
: 'write operation failed';
|
|
@@ -1230,7 +1221,9 @@ class BulkOperationBase {
|
|
|
1230
1221
|
null
|
|
1231
1222
|
);
|
|
1232
1223
|
return true;
|
|
1233
|
-
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (writeResult.getWriteConcernError()) {
|
|
1234
1227
|
handleCallback(
|
|
1235
1228
|
callback,
|
|
1236
1229
|
new BulkWriteError(toError(writeResult.getWriteConcernError()), writeResult),
|
package/lib/bulk/unordered.js
CHANGED
|
@@ -108,6 +108,14 @@ class UnorderedBulkOperation extends BulkOperationBase {
|
|
|
108
108
|
|
|
109
109
|
super(topology, collection, options, false);
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
handleWriteError(callback, writeResult) {
|
|
113
|
+
if (this.s.batches.length) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return super.handleWriteError(callback, writeResult);
|
|
118
|
+
}
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
/**
|
package/lib/change_stream.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const Denque = require('denque');
|
|
3
4
|
const EventEmitter = require('events');
|
|
4
5
|
const isResumableError = require('./error').isResumableError;
|
|
5
6
|
const MongoError = require('./core').MongoError;
|
|
6
7
|
const Cursor = require('./cursor');
|
|
7
8
|
const relayEvents = require('./core/utils').relayEvents;
|
|
8
9
|
const maxWireVersion = require('./core/utils').maxWireVersion;
|
|
10
|
+
const maybePromise = require('./utils').maybePromise;
|
|
9
11
|
const AggregateOperation = require('./operations/aggregate');
|
|
10
12
|
|
|
13
|
+
const kResumeQueue = Symbol('resumeQueue');
|
|
14
|
+
|
|
11
15
|
const CHANGE_STREAM_OPTIONS = ['resumeAfter', 'startAfter', 'startAtOperationTime', 'fullDocument'];
|
|
12
16
|
const CURSOR_OPTIONS = ['batchSize', 'maxAwaitTimeMS', 'collation', 'readPreference'].concat(
|
|
13
17
|
CHANGE_STREAM_OPTIONS
|
|
@@ -90,15 +94,17 @@ class ChangeStream extends EventEmitter {
|
|
|
90
94
|
this.options.readPreference = parent.s.readPreference;
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
this[kResumeQueue] = new Denque();
|
|
98
|
+
|
|
93
99
|
// Create contained Change Stream cursor
|
|
94
100
|
this.cursor = createChangeStreamCursor(this, options);
|
|
95
101
|
|
|
102
|
+
this.closed = false;
|
|
103
|
+
|
|
96
104
|
// Listen for any `change` listeners being added to ChangeStream
|
|
97
105
|
this.on('newListener', eventName => {
|
|
98
106
|
if (eventName === 'change' && this.cursor && this.listenerCount('change') === 0) {
|
|
99
|
-
this.cursor.on('data', change =>
|
|
100
|
-
processNewChange({ changeStream: this, change, eventEmitter: true })
|
|
101
|
-
);
|
|
107
|
+
this.cursor.on('data', change => processNewChange(this, change));
|
|
102
108
|
}
|
|
103
109
|
});
|
|
104
110
|
|
|
@@ -124,10 +130,15 @@ class ChangeStream extends EventEmitter {
|
|
|
124
130
|
* @function ChangeStream.prototype.hasNext
|
|
125
131
|
* @param {ChangeStream~resultCallback} [callback] The result callback.
|
|
126
132
|
* @throws {MongoError}
|
|
127
|
-
* @
|
|
133
|
+
* @returns {Promise|void} returns Promise if no callback passed
|
|
128
134
|
*/
|
|
129
135
|
hasNext(callback) {
|
|
130
|
-
return this.
|
|
136
|
+
return maybePromise(this.parent, callback, cb => {
|
|
137
|
+
getCursor(this, (err, cursor) => {
|
|
138
|
+
if (err) return cb(err); // failed to resume, raise an error
|
|
139
|
+
cursor.hasNext(cb);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
/**
|
|
@@ -135,31 +146,32 @@ class ChangeStream extends EventEmitter {
|
|
|
135
146
|
* @function ChangeStream.prototype.next
|
|
136
147
|
* @param {ChangeStream~resultCallback} [callback] The result callback.
|
|
137
148
|
* @throws {MongoError}
|
|
138
|
-
* @
|
|
149
|
+
* @returns {Promise|void} returns Promise if no callback passed
|
|
139
150
|
*/
|
|
140
151
|
next(callback) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
return maybePromise(this.parent, callback, cb => {
|
|
153
|
+
getCursor(this, (err, cursor) => {
|
|
154
|
+
if (err) return cb(err); // failed to resume, raise an error
|
|
155
|
+
cursor.next((error, change) => {
|
|
156
|
+
if (error) {
|
|
157
|
+
this[kResumeQueue].push(() => this.next(cb));
|
|
158
|
+
processError(this, error, cb);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
processNewChange(this, change, cb);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
151
165
|
}
|
|
152
166
|
|
|
153
167
|
/**
|
|
154
|
-
* Is the
|
|
168
|
+
* Is the change stream closed
|
|
155
169
|
* @method ChangeStream.prototype.isClosed
|
|
170
|
+
* @param {boolean} [checkCursor=true] also check if the underlying cursor is closed
|
|
156
171
|
* @return {boolean}
|
|
157
172
|
*/
|
|
158
173
|
isClosed() {
|
|
159
|
-
|
|
160
|
-
return this.cursor.isClosed();
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
174
|
+
return this.closed || (this.cursor && this.cursor.isClosed());
|
|
163
175
|
}
|
|
164
176
|
|
|
165
177
|
/**
|
|
@@ -169,31 +181,22 @@ class ChangeStream extends EventEmitter {
|
|
|
169
181
|
* @return {Promise} returns Promise if no callback passed
|
|
170
182
|
*/
|
|
171
183
|
close(callback) {
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
return this.promiseLibrary.resolve();
|
|
175
|
-
}
|
|
184
|
+
return maybePromise(this.parent, callback, cb => {
|
|
185
|
+
if (this.closed) return cb();
|
|
176
186
|
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
// flag the change stream as explicitly closed
|
|
188
|
+
this.closed = true;
|
|
179
189
|
|
|
180
|
-
|
|
181
|
-
return cursor.close(err => {
|
|
182
|
-
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
183
|
-
delete this.cursor;
|
|
190
|
+
if (!this.cursor) return cb();
|
|
184
191
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
192
|
+
// Tidy up the existing cursor
|
|
193
|
+
const cursor = this.cursor;
|
|
188
194
|
|
|
189
|
-
|
|
190
|
-
return new PromiseCtor((resolve, reject) => {
|
|
191
|
-
cursor.close(err => {
|
|
195
|
+
return cursor.close(err => {
|
|
192
196
|
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
193
|
-
|
|
197
|
+
this.cursor = undefined;
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
resolve();
|
|
199
|
+
return cb(err);
|
|
197
200
|
});
|
|
198
201
|
});
|
|
199
202
|
}
|
|
@@ -288,7 +291,9 @@ class ChangeStreamCursor extends Cursor {
|
|
|
288
291
|
['resumeAfter', 'startAfter', 'startAtOperationTime'].forEach(key => delete result[key]);
|
|
289
292
|
|
|
290
293
|
if (this.resumeToken) {
|
|
291
|
-
|
|
294
|
+
const resumeKey =
|
|
295
|
+
this.options.startAfter && !this.hasReceived ? 'startAfter' : 'resumeAfter';
|
|
296
|
+
result[resumeKey] = this.resumeToken;
|
|
292
297
|
} else if (this.startAtOperationTime && maxWireVersion(this.server) >= 7) {
|
|
293
298
|
result.startAtOperationTime = this.startAtOperationTime;
|
|
294
299
|
}
|
|
@@ -297,10 +302,30 @@ class ChangeStreamCursor extends Cursor {
|
|
|
297
302
|
return result;
|
|
298
303
|
}
|
|
299
304
|
|
|
305
|
+
cacheResumeToken(resumeToken) {
|
|
306
|
+
if (this.bufferedCount() === 0 && this.cursorState.postBatchResumeToken) {
|
|
307
|
+
this.resumeToken = this.cursorState.postBatchResumeToken;
|
|
308
|
+
} else {
|
|
309
|
+
this.resumeToken = resumeToken;
|
|
310
|
+
}
|
|
311
|
+
this.hasReceived = true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_processBatch(batchName, response) {
|
|
315
|
+
const cursor = response.cursor;
|
|
316
|
+
if (cursor.postBatchResumeToken) {
|
|
317
|
+
this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
|
|
318
|
+
|
|
319
|
+
if (cursor[batchName].length === 0) {
|
|
320
|
+
this.resumeToken = cursor.postBatchResumeToken;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
300
325
|
_initializeCursor(callback) {
|
|
301
326
|
super._initializeCursor((err, result) => {
|
|
302
327
|
if (err) {
|
|
303
|
-
callback(err
|
|
328
|
+
callback(err);
|
|
304
329
|
return;
|
|
305
330
|
}
|
|
306
331
|
|
|
@@ -315,15 +340,9 @@ class ChangeStreamCursor extends Cursor {
|
|
|
315
340
|
this.startAtOperationTime = response.operationTime;
|
|
316
341
|
}
|
|
317
342
|
|
|
318
|
-
|
|
319
|
-
if (cursor.postBatchResumeToken) {
|
|
320
|
-
this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
|
|
321
|
-
|
|
322
|
-
if (cursor.firstBatch.length === 0) {
|
|
323
|
-
this.resumeToken = cursor.postBatchResumeToken;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
343
|
+
this._processBatch('firstBatch', response);
|
|
326
344
|
|
|
345
|
+
this.emit('init', result);
|
|
327
346
|
this.emit('response');
|
|
328
347
|
callback(err, result);
|
|
329
348
|
});
|
|
@@ -332,19 +351,13 @@ class ChangeStreamCursor extends Cursor {
|
|
|
332
351
|
_getMore(callback) {
|
|
333
352
|
super._getMore((err, response) => {
|
|
334
353
|
if (err) {
|
|
335
|
-
callback(err
|
|
354
|
+
callback(err);
|
|
336
355
|
return;
|
|
337
356
|
}
|
|
338
357
|
|
|
339
|
-
|
|
340
|
-
if (cursor.postBatchResumeToken) {
|
|
341
|
-
this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
|
|
342
|
-
|
|
343
|
-
if (cursor.nextBatch.length === 0) {
|
|
344
|
-
this.resumeToken = cursor.postBatchResumeToken;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
358
|
+
this._processBatch('nextBatch', response);
|
|
347
359
|
|
|
360
|
+
this.emit('more', response);
|
|
348
361
|
this.emit('response');
|
|
349
362
|
callback(err, response);
|
|
350
363
|
});
|
|
@@ -367,6 +380,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
367
380
|
|
|
368
381
|
const pipeline = [{ $changeStream: changeStreamStageOptions }].concat(self.pipeline);
|
|
369
382
|
const cursorOptions = applyKnownOptions({}, options, CURSOR_OPTIONS);
|
|
383
|
+
|
|
370
384
|
const changeStreamCursor = new ChangeStreamCursor(
|
|
371
385
|
self.topology,
|
|
372
386
|
new AggregateOperation(self.parent, pipeline, options),
|
|
@@ -385,7 +399,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
385
399
|
*/
|
|
386
400
|
if (self.listenerCount('change') > 0) {
|
|
387
401
|
changeStreamCursor.on('data', function(change) {
|
|
388
|
-
processNewChange(
|
|
402
|
+
processNewChange(self, change);
|
|
389
403
|
});
|
|
390
404
|
}
|
|
391
405
|
|
|
@@ -417,7 +431,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
417
431
|
* @type {Error}
|
|
418
432
|
*/
|
|
419
433
|
changeStreamCursor.on('error', function(error) {
|
|
420
|
-
|
|
434
|
+
processError(self, error);
|
|
421
435
|
});
|
|
422
436
|
|
|
423
437
|
if (self.pipeDestinations) {
|
|
@@ -450,120 +464,141 @@ function waitForTopologyConnected(topology, options, callback) {
|
|
|
450
464
|
const timeout = options.timeout || SELECTION_TIMEOUT;
|
|
451
465
|
const readPreference = options.readPreference;
|
|
452
466
|
|
|
453
|
-
if (topology.isConnected({ readPreference })) return callback(
|
|
467
|
+
if (topology.isConnected({ readPreference })) return callback();
|
|
454
468
|
const hrElapsed = process.hrtime(start);
|
|
455
469
|
const elapsed = (hrElapsed[0] * 1e9 + hrElapsed[1]) / 1e6;
|
|
456
470
|
if (elapsed > timeout) return callback(new MongoError('Timed out waiting for connection'));
|
|
457
471
|
waitForTopologyConnected(topology, options, callback);
|
|
458
|
-
},
|
|
472
|
+
}, 500); // this is an arbitrary wait time to allow SDAM to transition
|
|
459
473
|
}
|
|
460
474
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const changeStream = args.changeStream;
|
|
464
|
-
const error = args.error;
|
|
465
|
-
const change = args.change;
|
|
466
|
-
const callback = args.callback;
|
|
467
|
-
const eventEmitter = args.eventEmitter || false;
|
|
475
|
+
function processNewChange(changeStream, change, callback) {
|
|
476
|
+
const cursor = changeStream.cursor;
|
|
468
477
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
478
|
+
if (changeStream.closed) {
|
|
479
|
+
if (callback) callback(new MongoError('ChangeStream is closed'));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
475
482
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
483
|
+
if (change && !change._id) {
|
|
484
|
+
const noResumeTokenError = new Error(
|
|
485
|
+
'A change stream document has been received that lacks a resume token (_id).'
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
if (!callback) return changeStream.emit('error', noResumeTokenError);
|
|
489
|
+
return callback(noResumeTokenError);
|
|
480
490
|
}
|
|
481
491
|
|
|
482
|
-
|
|
492
|
+
// cache the resume token
|
|
493
|
+
cursor.cacheResumeToken(change._id);
|
|
494
|
+
|
|
495
|
+
// wipe the startAtOperationTime if there was one so that there won't be a conflict
|
|
496
|
+
// between resumeToken and startAtOperationTime if we need to reconnect the cursor
|
|
497
|
+
changeStream.options.startAtOperationTime = undefined;
|
|
498
|
+
|
|
499
|
+
// Return the change
|
|
500
|
+
if (!callback) return changeStream.emit('change', change);
|
|
501
|
+
return callback(undefined, change);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function processError(changeStream, error, callback) {
|
|
483
505
|
const topology = changeStream.topology;
|
|
484
|
-
const
|
|
506
|
+
const cursor = changeStream.cursor;
|
|
485
507
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
508
|
+
// If the change stream has been closed explictly, do not process error.
|
|
509
|
+
if (changeStream.closed) {
|
|
510
|
+
if (callback) callback(new MongoError('ChangeStream is closed'));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
489
513
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
514
|
+
// if the resume succeeds, continue with the new cursor
|
|
515
|
+
function resumeWithCursor(newCursor) {
|
|
516
|
+
changeStream.cursor = newCursor;
|
|
517
|
+
processResumeQueue(changeStream);
|
|
518
|
+
}
|
|
494
519
|
|
|
495
|
-
|
|
496
|
-
|
|
520
|
+
// otherwise, raise an error and close the change stream
|
|
521
|
+
function unresumableError(err) {
|
|
522
|
+
if (!callback) {
|
|
523
|
+
changeStream.emit('error', err);
|
|
524
|
+
changeStream.emit('close');
|
|
525
|
+
}
|
|
526
|
+
processResumeQueue(changeStream, err);
|
|
527
|
+
changeStream.closed = true;
|
|
528
|
+
}
|
|
497
529
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
waitForTopologyConnected(topology, { readPreference: options.readPreference }, err => {
|
|
501
|
-
if (err) {
|
|
502
|
-
changeStream.emit('error', err);
|
|
503
|
-
changeStream.emit('close');
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
changeStream.cursor = createChangeStreamCursor(changeStream, cursor.resumeOptions);
|
|
507
|
-
});
|
|
530
|
+
if (cursor && isResumableError(error, maxWireVersion(cursor.server))) {
|
|
531
|
+
changeStream.cursor = undefined;
|
|
508
532
|
|
|
509
|
-
|
|
510
|
-
|
|
533
|
+
// stop listening to all events from old cursor
|
|
534
|
+
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
511
535
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (err) return callback(err, null);
|
|
536
|
+
// close internal cursor, ignore errors
|
|
537
|
+
cursor.close();
|
|
515
538
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
539
|
+
waitForTopologyConnected(topology, { readPreference: cursor.options.readPreference }, err => {
|
|
540
|
+
// if the topology can't reconnect, close the stream
|
|
541
|
+
if (err) return unresumableError(err);
|
|
519
542
|
|
|
520
|
-
|
|
521
|
-
|
|
543
|
+
// create a new cursor, preserving the old cursor's options
|
|
544
|
+
const newCursor = createChangeStreamCursor(changeStream, cursor.resumeOptions);
|
|
522
545
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (err) return reject(err);
|
|
526
|
-
resolve();
|
|
527
|
-
});
|
|
528
|
-
})
|
|
529
|
-
.then(
|
|
530
|
-
() => (changeStream.cursor = createChangeStreamCursor(changeStream, cursor.resumeOptions))
|
|
531
|
-
)
|
|
532
|
-
.then(() => changeStream.next());
|
|
533
|
-
}
|
|
546
|
+
// attempt to continue in emitter mode
|
|
547
|
+
if (!callback) return resumeWithCursor(newCursor);
|
|
534
548
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
549
|
+
// attempt to continue in iterator mode
|
|
550
|
+
newCursor.hasNext(err => {
|
|
551
|
+
// if there's an error immediately after resuming, close the stream
|
|
552
|
+
if (err) return unresumableError(err);
|
|
553
|
+
resumeWithCursor(newCursor);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
return;
|
|
538
557
|
}
|
|
539
558
|
|
|
540
|
-
changeStream.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const noResumeTokenError = new Error(
|
|
544
|
-
'A change stream document has been received that lacks a resume token (_id).'
|
|
545
|
-
);
|
|
559
|
+
if (!callback) return changeStream.emit('error', error);
|
|
560
|
+
return callback(error);
|
|
561
|
+
}
|
|
546
562
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
563
|
+
/**
|
|
564
|
+
* Safely provides a cursor across resume attempts
|
|
565
|
+
*
|
|
566
|
+
* @param {ChangeStream} changeStream the parent ChangeStream
|
|
567
|
+
* @param {function} callback gets the cursor or error
|
|
568
|
+
* @param {ChangeStreamCursor} [oldCursor] when resuming from an error, carry over options from previous cursor
|
|
569
|
+
*/
|
|
570
|
+
function getCursor(changeStream, callback) {
|
|
571
|
+
if (changeStream.isClosed()) {
|
|
572
|
+
callback(new MongoError('ChangeStream is closed.'));
|
|
573
|
+
return;
|
|
550
574
|
}
|
|
551
575
|
|
|
552
|
-
//
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
cursor.resumeToken = change._id;
|
|
576
|
+
// if a cursor exists and it is open, return it
|
|
577
|
+
if (changeStream.cursor) {
|
|
578
|
+
callback(undefined, changeStream.cursor);
|
|
579
|
+
return;
|
|
557
580
|
}
|
|
558
581
|
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
582
|
+
// no cursor, queue callback until topology reconnects
|
|
583
|
+
changeStream[kResumeQueue].push(callback);
|
|
584
|
+
}
|
|
562
585
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
586
|
+
/**
|
|
587
|
+
* Drain the resume queue when a new has become available
|
|
588
|
+
*
|
|
589
|
+
* @param {ChangeStream} changeStream the parent ChangeStream
|
|
590
|
+
* @param {ChangeStreamCursor?} changeStream.cursor the new cursor
|
|
591
|
+
* @param {Error} [err] error getting a new cursor
|
|
592
|
+
*/
|
|
593
|
+
function processResumeQueue(changeStream, err) {
|
|
594
|
+
while (changeStream[kResumeQueue].length) {
|
|
595
|
+
const request = changeStream[kResumeQueue].pop();
|
|
596
|
+
if (changeStream.isClosed() && !err) {
|
|
597
|
+
request(new MongoError('Change Stream is not open.'));
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
request(err, changeStream.cursor);
|
|
601
|
+
}
|
|
567
602
|
}
|
|
568
603
|
|
|
569
604
|
/**
|
|
@@ -198,6 +198,10 @@ class ConnectionPool extends EventEmitter {
|
|
|
198
198
|
return this[kConnections].length;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
get waitQueueSize() {
|
|
202
|
+
return this[kWaitQueue].length;
|
|
203
|
+
}
|
|
204
|
+
|
|
201
205
|
/**
|
|
202
206
|
* Check a connection out of this pool. The connection will continue to be tracked, but no reference to it
|
|
203
207
|
* will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
|
|
@@ -295,7 +299,7 @@ class ConnectionPool extends EventEmitter {
|
|
|
295
299
|
this[kCancellationToken].emit('cancel');
|
|
296
300
|
|
|
297
301
|
// drain the wait queue
|
|
298
|
-
while (this
|
|
302
|
+
while (this.waitQueueSize) {
|
|
299
303
|
const waitQueueMember = this[kWaitQueue].pop();
|
|
300
304
|
clearTimeout(waitQueueMember.timer);
|
|
301
305
|
if (!waitQueueMember[kCancelled]) {
|
|
@@ -449,13 +453,17 @@ function processWaitQueue(pool) {
|
|
|
449
453
|
return;
|
|
450
454
|
}
|
|
451
455
|
|
|
452
|
-
while (pool
|
|
456
|
+
while (pool.waitQueueSize) {
|
|
453
457
|
const waitQueueMember = pool[kWaitQueue].peekFront();
|
|
454
458
|
if (waitQueueMember[kCancelled]) {
|
|
455
459
|
pool[kWaitQueue].shift();
|
|
456
460
|
continue;
|
|
457
461
|
}
|
|
458
462
|
|
|
463
|
+
if (!pool.availableConnectionCount) {
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
|
|
459
467
|
const connection = pool[kConnections].shift();
|
|
460
468
|
const isStale = connectionIsStale(pool, connection);
|
|
461
469
|
const isIdle = connectionIsIdle(pool, connection);
|
|
@@ -472,7 +480,7 @@ function processWaitQueue(pool) {
|
|
|
472
480
|
}
|
|
473
481
|
|
|
474
482
|
const maxPoolSize = pool.options.maxPoolSize;
|
|
475
|
-
if (pool
|
|
483
|
+
if (pool.waitQueueSize && (maxPoolSize <= 0 || pool.totalConnectionCount < maxPoolSize)) {
|
|
476
484
|
createConnection(pool, (err, connection) => {
|
|
477
485
|
const waitQueueMember = pool[kWaitQueue].shift();
|
|
478
486
|
if (waitQueueMember == null) {
|
package/lib/collection.js
CHANGED
|
@@ -310,7 +310,7 @@ const DEPRECATED_FIND_OPTIONS = ['maxScan', 'fields', 'snapshot'];
|
|
|
310
310
|
* @param {(ReadPreference|string)} [options.readPreference] The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
|
|
311
311
|
* @param {boolean} [options.partial=false] Specify if the cursor should return partial results when querying against a sharded system
|
|
312
312
|
* @param {number} [options.maxTimeMS] Number of milliseconds to wait before aborting the query.
|
|
313
|
-
* @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `
|
|
313
|
+
* @param {number} [options.maxAwaitTimeMS] The maximum amount of time for the server to wait on new documents to satisfy a tailable cursor query. Requires `tailable` and `awaitData` to be true
|
|
314
314
|
* @param {boolean} [options.noCursorTimeout] The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. Set this option to prevent that.
|
|
315
315
|
* @param {object} [options.collation] Specify collation (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields).
|
|
316
316
|
* @param {ClientSession} [options.session] optional session to use for this operation
|