mongodb 3.5.5 → 3.5.9
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 +61 -0
- package/lib/bulk/common.js +4 -11
- package/lib/bulk/unordered.js +8 -0
- package/lib/change_stream.js +194 -151
- package/lib/cmap/connection.js +6 -4
- package/lib/cmap/connection_pool.js +11 -3
- package/lib/collection.js +1 -1
- package/lib/core/connection/apm.js +1 -1
- package/lib/core/connection/pool.js +2 -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/monitor.js +51 -58
- package/lib/core/sdam/server.js +8 -2
- package/lib/core/sdam/server_description.js +2 -1
- package/lib/core/sdam/server_selection.js +29 -37
- package/lib/core/sessions.js +25 -14
- package/lib/core/topologies/replset.js +4 -4
- package/lib/core/uri_parser.js +2 -2
- package/lib/core/utils.js +13 -23
- package/lib/core/wireprotocol/get_more.js +6 -1
- package/lib/cursor.js +11 -38
- package/lib/error.js +26 -33
- package/lib/mongo_client.js +21 -2
- package/lib/operations/connect.js +11 -0
- package/lib/operations/cursor_ops.js +2 -3
- package/lib/operations/db_ops.js +0 -361
- package/lib/topologies/native_topology.js +12 -2
- package/lib/utils.js +96 -1
- package/package.json +6 -5
package/HISTORY.md
CHANGED
|
@@ -2,6 +2,67 @@
|
|
|
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.9"></a>
|
|
6
|
+
## [3.5.9](https://github.com/mongodb/node-mongodb-native/compare/v3.5.8...v3.5.9) (2020-06-12)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* don't try to calculate sMax if there are no viable servers ([be51347](https://github.com/mongodb/node-mongodb-native/commit/be51347))
|
|
12
|
+
* use async interruptable interval for server monitoring ([1f855a4](https://github.com/mongodb/node-mongodb-native/commit/1f855a4))
|
|
13
|
+
* use duration of handshake if no previous roundTripTime exists ([ddfa41b](https://github.com/mongodb/node-mongodb-native/commit/ddfa41b))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* introduce an interruptable async interval timer ([9e12cd5](https://github.com/mongodb/node-mongodb-native/commit/9e12cd5))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
<a name="3.5.8"></a>
|
|
23
|
+
## [3.5.8](https://github.com/mongodb/node-mongodb-native/compare/v3.5.7...v3.5.8) (2020-05-28)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* always clear cancelled wait queue members during processing ([0394f9d](https://github.com/mongodb/node-mongodb-native/commit/0394f9d))
|
|
29
|
+
* always include `writeErrors` on a `BulkWriteError` instance ([58b4f94](https://github.com/mongodb/node-mongodb-native/commit/58b4f94))
|
|
30
|
+
* ensure implicit sessions are ended consistently ([5c6fda1](https://github.com/mongodb/node-mongodb-native/commit/5c6fda1))
|
|
31
|
+
* filter servers before applying reducers ([4faf9f5](https://github.com/mongodb/node-mongodb-native/commit/4faf9f5))
|
|
32
|
+
* unordered bulk write should attempt to execute all batches ([6cee96b](https://github.com/mongodb/node-mongodb-native/commit/6cee96b))
|
|
33
|
+
* **ChangeStream:** should resume from errors when iterating ([5ecf18e](https://github.com/mongodb/node-mongodb-native/commit/5ecf18e))
|
|
34
|
+
* 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))
|
|
35
|
+
* **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)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<a name="3.5.7"></a>
|
|
40
|
+
## [3.5.7](https://github.com/mongodb/node-mongodb-native/compare/v3.5.6...v3.5.7) (2020-04-29)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Bug Fixes
|
|
44
|
+
|
|
45
|
+
* limit growth of server sessions through lazy acquisition ([3d05a6d](https://github.com/mongodb/node-mongodb-native/commit/3d05a6d))
|
|
46
|
+
* remove circular dependency warnings on node 14 ([56a1b8a](https://github.com/mongodb/node-mongodb-native/commit/56a1b8a))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
<a name="3.5.6"></a>
|
|
51
|
+
## [3.5.6](https://github.com/mongodb/node-mongodb-native/compare/v3.5.5...v3.5.6) (2020-04-14)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Bug Fixes
|
|
55
|
+
|
|
56
|
+
* always return empty array for selection on unknown topology ([f9e786a](https://github.com/mongodb/node-mongodb-native/commit/f9e786a))
|
|
57
|
+
* createCollection only uses listCollections in strict mode ([d368f12](https://github.com/mongodb/node-mongodb-native/commit/d368f12))
|
|
58
|
+
* don't throw if `withTransaction()` callback rejects with a null reason ([153646c](https://github.com/mongodb/node-mongodb-native/commit/153646c))
|
|
59
|
+
* only mark server session dirty if the client session is alive ([611be8d](https://github.com/mongodb/node-mongodb-native/commit/611be8d))
|
|
60
|
+
* polyfill for util.promisify ([1c4cf6c](https://github.com/mongodb/node-mongodb-native/commit/1c4cf6c))
|
|
61
|
+
* single `readPreferenceTags` should be parsed as an array ([a50611b](https://github.com/mongodb/node-mongodb-native/commit/a50611b))
|
|
62
|
+
* **cursor:** transforms should only be applied once to documents ([704f30a](https://github.com/mongodb/node-mongodb-native/commit/704f30a))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
5
66
|
<a name="3.5.5"></a>
|
|
6
67
|
## [3.5.5](https://github.com/mongodb/node-mongodb-native/compare/v3.5.4...v3.5.5) (2020-03-11)
|
|
7
68
|
|
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,19 @@
|
|
|
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;
|
|
11
|
+
const now = require('./utils').now;
|
|
12
|
+
const calculateDurationInMs = require('./utils').calculateDurationInMs;
|
|
9
13
|
const AggregateOperation = require('./operations/aggregate');
|
|
10
14
|
|
|
15
|
+
const kResumeQueue = Symbol('resumeQueue');
|
|
16
|
+
|
|
11
17
|
const CHANGE_STREAM_OPTIONS = ['resumeAfter', 'startAfter', 'startAtOperationTime', 'fullDocument'];
|
|
12
18
|
const CURSOR_OPTIONS = ['batchSize', 'maxAwaitTimeMS', 'collation', 'readPreference'].concat(
|
|
13
19
|
CHANGE_STREAM_OPTIONS
|
|
@@ -90,15 +96,17 @@ class ChangeStream extends EventEmitter {
|
|
|
90
96
|
this.options.readPreference = parent.s.readPreference;
|
|
91
97
|
}
|
|
92
98
|
|
|
99
|
+
this[kResumeQueue] = new Denque();
|
|
100
|
+
|
|
93
101
|
// Create contained Change Stream cursor
|
|
94
102
|
this.cursor = createChangeStreamCursor(this, options);
|
|
95
103
|
|
|
104
|
+
this.closed = false;
|
|
105
|
+
|
|
96
106
|
// Listen for any `change` listeners being added to ChangeStream
|
|
97
107
|
this.on('newListener', eventName => {
|
|
98
108
|
if (eventName === 'change' && this.cursor && this.listenerCount('change') === 0) {
|
|
99
|
-
this.cursor.on('data', change =>
|
|
100
|
-
processNewChange({ changeStream: this, change, eventEmitter: true })
|
|
101
|
-
);
|
|
109
|
+
this.cursor.on('data', change => processNewChange(this, change));
|
|
102
110
|
}
|
|
103
111
|
});
|
|
104
112
|
|
|
@@ -124,10 +132,15 @@ class ChangeStream extends EventEmitter {
|
|
|
124
132
|
* @function ChangeStream.prototype.hasNext
|
|
125
133
|
* @param {ChangeStream~resultCallback} [callback] The result callback.
|
|
126
134
|
* @throws {MongoError}
|
|
127
|
-
* @
|
|
135
|
+
* @returns {Promise|void} returns Promise if no callback passed
|
|
128
136
|
*/
|
|
129
137
|
hasNext(callback) {
|
|
130
|
-
return this.
|
|
138
|
+
return maybePromise(this.parent, callback, cb => {
|
|
139
|
+
getCursor(this, (err, cursor) => {
|
|
140
|
+
if (err) return cb(err); // failed to resume, raise an error
|
|
141
|
+
cursor.hasNext(cb);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
131
144
|
}
|
|
132
145
|
|
|
133
146
|
/**
|
|
@@ -135,31 +148,32 @@ class ChangeStream extends EventEmitter {
|
|
|
135
148
|
* @function ChangeStream.prototype.next
|
|
136
149
|
* @param {ChangeStream~resultCallback} [callback] The result callback.
|
|
137
150
|
* @throws {MongoError}
|
|
138
|
-
* @
|
|
151
|
+
* @returns {Promise|void} returns Promise if no callback passed
|
|
139
152
|
*/
|
|
140
153
|
next(callback) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
154
|
+
return maybePromise(this.parent, callback, cb => {
|
|
155
|
+
getCursor(this, (err, cursor) => {
|
|
156
|
+
if (err) return cb(err); // failed to resume, raise an error
|
|
157
|
+
cursor.next((error, change) => {
|
|
158
|
+
if (error) {
|
|
159
|
+
this[kResumeQueue].push(() => this.next(cb));
|
|
160
|
+
processError(this, error, cb);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
processNewChange(this, change, cb);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
151
167
|
}
|
|
152
168
|
|
|
153
169
|
/**
|
|
154
|
-
* Is the
|
|
170
|
+
* Is the change stream closed
|
|
155
171
|
* @method ChangeStream.prototype.isClosed
|
|
172
|
+
* @param {boolean} [checkCursor=true] also check if the underlying cursor is closed
|
|
156
173
|
* @return {boolean}
|
|
157
174
|
*/
|
|
158
175
|
isClosed() {
|
|
159
|
-
|
|
160
|
-
return this.cursor.isClosed();
|
|
161
|
-
}
|
|
162
|
-
return true;
|
|
176
|
+
return this.closed || (this.cursor && this.cursor.isClosed());
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
/**
|
|
@@ -169,31 +183,22 @@ class ChangeStream extends EventEmitter {
|
|
|
169
183
|
* @return {Promise} returns Promise if no callback passed
|
|
170
184
|
*/
|
|
171
185
|
close(callback) {
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
return this.promiseLibrary.resolve();
|
|
175
|
-
}
|
|
186
|
+
return maybePromise(this.parent, callback, cb => {
|
|
187
|
+
if (this.closed) return cb();
|
|
176
188
|
|
|
177
|
-
|
|
178
|
-
|
|
189
|
+
// flag the change stream as explicitly closed
|
|
190
|
+
this.closed = true;
|
|
179
191
|
|
|
180
|
-
|
|
181
|
-
return cursor.close(err => {
|
|
182
|
-
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
183
|
-
delete this.cursor;
|
|
192
|
+
if (!this.cursor) return cb();
|
|
184
193
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
194
|
+
// Tidy up the existing cursor
|
|
195
|
+
const cursor = this.cursor;
|
|
188
196
|
|
|
189
|
-
|
|
190
|
-
return new PromiseCtor((resolve, reject) => {
|
|
191
|
-
cursor.close(err => {
|
|
197
|
+
return cursor.close(err => {
|
|
192
198
|
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
193
|
-
|
|
199
|
+
this.cursor = undefined;
|
|
194
200
|
|
|
195
|
-
|
|
196
|
-
resolve();
|
|
201
|
+
return cb(err);
|
|
197
202
|
});
|
|
198
203
|
});
|
|
199
204
|
}
|
|
@@ -288,7 +293,9 @@ class ChangeStreamCursor extends Cursor {
|
|
|
288
293
|
['resumeAfter', 'startAfter', 'startAtOperationTime'].forEach(key => delete result[key]);
|
|
289
294
|
|
|
290
295
|
if (this.resumeToken) {
|
|
291
|
-
|
|
296
|
+
const resumeKey =
|
|
297
|
+
this.options.startAfter && !this.hasReceived ? 'startAfter' : 'resumeAfter';
|
|
298
|
+
result[resumeKey] = this.resumeToken;
|
|
292
299
|
} else if (this.startAtOperationTime && maxWireVersion(this.server) >= 7) {
|
|
293
300
|
result.startAtOperationTime = this.startAtOperationTime;
|
|
294
301
|
}
|
|
@@ -297,10 +304,30 @@ class ChangeStreamCursor extends Cursor {
|
|
|
297
304
|
return result;
|
|
298
305
|
}
|
|
299
306
|
|
|
307
|
+
cacheResumeToken(resumeToken) {
|
|
308
|
+
if (this.bufferedCount() === 0 && this.cursorState.postBatchResumeToken) {
|
|
309
|
+
this.resumeToken = this.cursorState.postBatchResumeToken;
|
|
310
|
+
} else {
|
|
311
|
+
this.resumeToken = resumeToken;
|
|
312
|
+
}
|
|
313
|
+
this.hasReceived = true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
_processBatch(batchName, response) {
|
|
317
|
+
const cursor = response.cursor;
|
|
318
|
+
if (cursor.postBatchResumeToken) {
|
|
319
|
+
this.cursorState.postBatchResumeToken = cursor.postBatchResumeToken;
|
|
320
|
+
|
|
321
|
+
if (cursor[batchName].length === 0) {
|
|
322
|
+
this.resumeToken = cursor.postBatchResumeToken;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
300
327
|
_initializeCursor(callback) {
|
|
301
328
|
super._initializeCursor((err, result) => {
|
|
302
329
|
if (err) {
|
|
303
|
-
callback(err
|
|
330
|
+
callback(err);
|
|
304
331
|
return;
|
|
305
332
|
}
|
|
306
333
|
|
|
@@ -315,15 +342,9 @@ class ChangeStreamCursor extends Cursor {
|
|
|
315
342
|
this.startAtOperationTime = response.operationTime;
|
|
316
343
|
}
|
|
317
344
|
|
|
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
|
-
}
|
|
345
|
+
this._processBatch('firstBatch', response);
|
|
326
346
|
|
|
347
|
+
this.emit('init', result);
|
|
327
348
|
this.emit('response');
|
|
328
349
|
callback(err, result);
|
|
329
350
|
});
|
|
@@ -332,19 +353,13 @@ class ChangeStreamCursor extends Cursor {
|
|
|
332
353
|
_getMore(callback) {
|
|
333
354
|
super._getMore((err, response) => {
|
|
334
355
|
if (err) {
|
|
335
|
-
callback(err
|
|
356
|
+
callback(err);
|
|
336
357
|
return;
|
|
337
358
|
}
|
|
338
359
|
|
|
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
|
-
}
|
|
360
|
+
this._processBatch('nextBatch', response);
|
|
347
361
|
|
|
362
|
+
this.emit('more', response);
|
|
348
363
|
this.emit('response');
|
|
349
364
|
callback(err, response);
|
|
350
365
|
});
|
|
@@ -367,6 +382,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
367
382
|
|
|
368
383
|
const pipeline = [{ $changeStream: changeStreamStageOptions }].concat(self.pipeline);
|
|
369
384
|
const cursorOptions = applyKnownOptions({}, options, CURSOR_OPTIONS);
|
|
385
|
+
|
|
370
386
|
const changeStreamCursor = new ChangeStreamCursor(
|
|
371
387
|
self.topology,
|
|
372
388
|
new AggregateOperation(self.parent, pipeline, options),
|
|
@@ -385,7 +401,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
385
401
|
*/
|
|
386
402
|
if (self.listenerCount('change') > 0) {
|
|
387
403
|
changeStreamCursor.on('data', function(change) {
|
|
388
|
-
processNewChange(
|
|
404
|
+
processNewChange(self, change);
|
|
389
405
|
});
|
|
390
406
|
}
|
|
391
407
|
|
|
@@ -417,7 +433,7 @@ function createChangeStreamCursor(self, options) {
|
|
|
417
433
|
* @type {Error}
|
|
418
434
|
*/
|
|
419
435
|
changeStreamCursor.on('error', function(error) {
|
|
420
|
-
|
|
436
|
+
processError(self, error);
|
|
421
437
|
});
|
|
422
438
|
|
|
423
439
|
if (self.pipeDestinations) {
|
|
@@ -445,125 +461,152 @@ function applyKnownOptions(target, source, optionNames) {
|
|
|
445
461
|
const SELECTION_TIMEOUT = 30000;
|
|
446
462
|
function waitForTopologyConnected(topology, options, callback) {
|
|
447
463
|
setTimeout(() => {
|
|
448
|
-
if (options && options.start == null)
|
|
449
|
-
|
|
464
|
+
if (options && options.start == null) {
|
|
465
|
+
options.start = now();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const start = options.start || now();
|
|
450
469
|
const timeout = options.timeout || SELECTION_TIMEOUT;
|
|
451
470
|
const readPreference = options.readPreference;
|
|
471
|
+
if (topology.isConnected({ readPreference })) {
|
|
472
|
+
return callback();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (calculateDurationInMs(start) > timeout) {
|
|
476
|
+
return callback(new MongoError('Timed out waiting for connection'));
|
|
477
|
+
}
|
|
452
478
|
|
|
453
|
-
if (topology.isConnected({ readPreference })) return callback(null, null);
|
|
454
|
-
const hrElapsed = process.hrtime(start);
|
|
455
|
-
const elapsed = (hrElapsed[0] * 1e9 + hrElapsed[1]) / 1e6;
|
|
456
|
-
if (elapsed > timeout) return callback(new MongoError('Timed out waiting for connection'));
|
|
457
479
|
waitForTopologyConnected(topology, options, callback);
|
|
458
|
-
},
|
|
480
|
+
}, 500); // this is an arbitrary wait time to allow SDAM to transition
|
|
459
481
|
}
|
|
460
482
|
|
|
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;
|
|
483
|
+
function processNewChange(changeStream, change, callback) {
|
|
484
|
+
const cursor = changeStream.cursor;
|
|
468
485
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
486
|
+
if (changeStream.closed) {
|
|
487
|
+
if (callback) callback(new MongoError('ChangeStream is closed'));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (change && !change._id) {
|
|
492
|
+
const noResumeTokenError = new Error(
|
|
493
|
+
'A change stream document has been received that lacks a resume token (_id).'
|
|
494
|
+
);
|
|
475
495
|
|
|
476
|
-
|
|
477
|
-
return
|
|
478
|
-
? callback(error, null)
|
|
479
|
-
: changeStream.promiseLibrary.reject(error);
|
|
496
|
+
if (!callback) return changeStream.emit('error', noResumeTokenError);
|
|
497
|
+
return callback(noResumeTokenError);
|
|
480
498
|
}
|
|
481
499
|
|
|
482
|
-
|
|
500
|
+
// cache the resume token
|
|
501
|
+
cursor.cacheResumeToken(change._id);
|
|
502
|
+
|
|
503
|
+
// wipe the startAtOperationTime if there was one so that there won't be a conflict
|
|
504
|
+
// between resumeToken and startAtOperationTime if we need to reconnect the cursor
|
|
505
|
+
changeStream.options.startAtOperationTime = undefined;
|
|
506
|
+
|
|
507
|
+
// Return the change
|
|
508
|
+
if (!callback) return changeStream.emit('change', change);
|
|
509
|
+
return callback(undefined, change);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function processError(changeStream, error, callback) {
|
|
483
513
|
const topology = changeStream.topology;
|
|
484
|
-
const
|
|
514
|
+
const cursor = changeStream.cursor;
|
|
485
515
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
516
|
+
// If the change stream has been closed explictly, do not process error.
|
|
517
|
+
if (changeStream.closed) {
|
|
518
|
+
if (callback) callback(new MongoError('ChangeStream is closed'));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
489
521
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
522
|
+
// if the resume succeeds, continue with the new cursor
|
|
523
|
+
function resumeWithCursor(newCursor) {
|
|
524
|
+
changeStream.cursor = newCursor;
|
|
525
|
+
processResumeQueue(changeStream);
|
|
526
|
+
}
|
|
494
527
|
|
|
495
|
-
|
|
496
|
-
|
|
528
|
+
// otherwise, raise an error and close the change stream
|
|
529
|
+
function unresumableError(err) {
|
|
530
|
+
if (!callback) {
|
|
531
|
+
changeStream.emit('error', err);
|
|
532
|
+
changeStream.emit('close');
|
|
533
|
+
}
|
|
534
|
+
processResumeQueue(changeStream, err);
|
|
535
|
+
changeStream.closed = true;
|
|
536
|
+
}
|
|
497
537
|
|
|
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
|
-
});
|
|
538
|
+
if (cursor && isResumableError(error, maxWireVersion(cursor.server))) {
|
|
539
|
+
changeStream.cursor = undefined;
|
|
508
540
|
|
|
509
|
-
|
|
510
|
-
|
|
541
|
+
// stop listening to all events from old cursor
|
|
542
|
+
['data', 'close', 'end', 'error'].forEach(event => cursor.removeAllListeners(event));
|
|
511
543
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (err) return callback(err, null);
|
|
544
|
+
// close internal cursor, ignore errors
|
|
545
|
+
cursor.close();
|
|
515
546
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
547
|
+
waitForTopologyConnected(topology, { readPreference: cursor.options.readPreference }, err => {
|
|
548
|
+
// if the topology can't reconnect, close the stream
|
|
549
|
+
if (err) return unresumableError(err);
|
|
519
550
|
|
|
520
|
-
|
|
521
|
-
|
|
551
|
+
// create a new cursor, preserving the old cursor's options
|
|
552
|
+
const newCursor = createChangeStreamCursor(changeStream, cursor.resumeOptions);
|
|
522
553
|
|
|
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
|
-
}
|
|
554
|
+
// attempt to continue in emitter mode
|
|
555
|
+
if (!callback) return resumeWithCursor(newCursor);
|
|
534
556
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
557
|
+
// attempt to continue in iterator mode
|
|
558
|
+
newCursor.hasNext(err => {
|
|
559
|
+
// if there's an error immediately after resuming, close the stream
|
|
560
|
+
if (err) return unresumableError(err);
|
|
561
|
+
resumeWithCursor(newCursor);
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
538
565
|
}
|
|
539
566
|
|
|
540
|
-
changeStream.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const noResumeTokenError = new Error(
|
|
544
|
-
'A change stream document has been received that lacks a resume token (_id).'
|
|
545
|
-
);
|
|
567
|
+
if (!callback) return changeStream.emit('error', error);
|
|
568
|
+
return callback(error);
|
|
569
|
+
}
|
|
546
570
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
571
|
+
/**
|
|
572
|
+
* Safely provides a cursor across resume attempts
|
|
573
|
+
*
|
|
574
|
+
* @param {ChangeStream} changeStream the parent ChangeStream
|
|
575
|
+
* @param {function} callback gets the cursor or error
|
|
576
|
+
* @param {ChangeStreamCursor} [oldCursor] when resuming from an error, carry over options from previous cursor
|
|
577
|
+
*/
|
|
578
|
+
function getCursor(changeStream, callback) {
|
|
579
|
+
if (changeStream.isClosed()) {
|
|
580
|
+
callback(new MongoError('ChangeStream is closed.'));
|
|
581
|
+
return;
|
|
550
582
|
}
|
|
551
583
|
|
|
552
|
-
//
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
cursor.resumeToken = change._id;
|
|
584
|
+
// if a cursor exists and it is open, return it
|
|
585
|
+
if (changeStream.cursor) {
|
|
586
|
+
callback(undefined, changeStream.cursor);
|
|
587
|
+
return;
|
|
557
588
|
}
|
|
558
589
|
|
|
559
|
-
//
|
|
560
|
-
|
|
561
|
-
|
|
590
|
+
// no cursor, queue callback until topology reconnects
|
|
591
|
+
changeStream[kResumeQueue].push(callback);
|
|
592
|
+
}
|
|
562
593
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
594
|
+
/**
|
|
595
|
+
* Drain the resume queue when a new has become available
|
|
596
|
+
*
|
|
597
|
+
* @param {ChangeStream} changeStream the parent ChangeStream
|
|
598
|
+
* @param {ChangeStreamCursor?} changeStream.cursor the new cursor
|
|
599
|
+
* @param {Error} [err] error getting a new cursor
|
|
600
|
+
*/
|
|
601
|
+
function processResumeQueue(changeStream, err) {
|
|
602
|
+
while (changeStream[kResumeQueue].length) {
|
|
603
|
+
const request = changeStream[kResumeQueue].pop();
|
|
604
|
+
if (changeStream.isClosed() && !err) {
|
|
605
|
+
request(new MongoError('Change Stream is not open.'));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
request(err, changeStream.cursor);
|
|
609
|
+
}
|
|
567
610
|
}
|
|
568
611
|
|
|
569
612
|
/**
|
package/lib/cmap/connection.js
CHANGED
|
@@ -11,6 +11,8 @@ const wp = require('../core/wireprotocol');
|
|
|
11
11
|
const apm = require('../core/connection/apm');
|
|
12
12
|
const updateSessionFromResponse = require('../core/sessions').updateSessionFromResponse;
|
|
13
13
|
const uuidV4 = require('../core/utils').uuidV4;
|
|
14
|
+
const now = require('../utils').now;
|
|
15
|
+
const calculateDurationInMs = require('../utils').calculateDurationInMs;
|
|
14
16
|
|
|
15
17
|
const kStream = Symbol('stream');
|
|
16
18
|
const kQueue = Symbol('queue');
|
|
@@ -37,7 +39,7 @@ class Connection extends EventEmitter {
|
|
|
37
39
|
|
|
38
40
|
this[kDescription] = new StreamDescription(this.address, options);
|
|
39
41
|
this[kGeneration] = options.generation;
|
|
40
|
-
this[kLastUseTime] =
|
|
42
|
+
this[kLastUseTime] = now();
|
|
41
43
|
|
|
42
44
|
// retain a reference to an `AutoEncrypter` if present
|
|
43
45
|
if (options.autoEncrypter) {
|
|
@@ -108,7 +110,7 @@ class Connection extends EventEmitter {
|
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
get idleTime() {
|
|
111
|
-
return
|
|
113
|
+
return calculateDurationInMs(this[kLastUseTime]);
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
get clusterTime() {
|
|
@@ -120,7 +122,7 @@ class Connection extends EventEmitter {
|
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
markAvailable() {
|
|
123
|
-
this[kLastUseTime] =
|
|
125
|
+
this[kLastUseTime] = now();
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
destroy(options, callback) {
|
|
@@ -326,7 +328,7 @@ function write(command, options, callback) {
|
|
|
326
328
|
if (this.monitorCommands) {
|
|
327
329
|
this.emit('commandStarted', new apm.CommandStartedEvent(this, command));
|
|
328
330
|
|
|
329
|
-
operationDescription.started =
|
|
331
|
+
operationDescription.started = now();
|
|
330
332
|
operationDescription.cb = (err, reply) => {
|
|
331
333
|
if (err) {
|
|
332
334
|
this.emit(
|