Skip to content

Commit f2b2816

Browse files
authored
Merge pull request Automattic#14982 from Automattic/vkarpov15/Automatticgh-14966
fix(cursor): close underlying query cursor when calling destroy()
2 parents 5bc09d0 + b9c34d5 commit f2b2816

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

lib/cursor/aggregationCursor.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,37 @@ AggregationCursor.prototype.close = async function close() {
196196
this.emit('close');
197197
};
198198

199+
/**
200+
* Marks this cursor as destroyed. Will stop streaming and subsequent calls to
201+
* `next()` will error.
202+
*
203+
* @return {this}
204+
* @api private
205+
* @method _destroy
206+
*/
207+
208+
AggregationCursor.prototype._destroy = function _destroy(_err, callback) {
209+
let waitForCursor = null;
210+
if (!this.cursor) {
211+
waitForCursor = new Promise((resolve) => {
212+
this.once('cursor', resolve);
213+
});
214+
} else {
215+
waitForCursor = Promise.resolve();
216+
}
217+
218+
waitForCursor
219+
.then(() => this.cursor.close())
220+
.then(() => {
221+
this._closed = true;
222+
callback();
223+
})
224+
.catch(error => {
225+
callback(error);
226+
});
227+
return this;
228+
};
229+
199230
/**
200231
* Get the next document from this cursor. Will return `null` when there are
201232
* no documents left.

lib/cursor/queryCursor.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,39 @@ QueryCursor.prototype.close = async function close() {
238238
}
239239
};
240240

241+
/**
242+
* Marks this cursor as destroyed. Will stop streaming and subsequent calls to
243+
* `next()` will error.
244+
*
245+
* @return {this}
246+
* @api private
247+
* @method _destroy
248+
*/
249+
250+
QueryCursor.prototype._destroy = function _destroy(_err, callback) {
251+
let waitForCursor = null;
252+
if (!this.cursor) {
253+
waitForCursor = new Promise((resolve) => {
254+
this.once('cursor', resolve);
255+
});
256+
} else {
257+
waitForCursor = Promise.resolve();
258+
}
259+
260+
waitForCursor
261+
.then(() => {
262+
this.cursor.close();
263+
})
264+
.then(() => {
265+
this._closed = true;
266+
callback();
267+
})
268+
.catch(error => {
269+
callback(error);
270+
});
271+
return this;
272+
};
273+
241274
/**
242275
* Rewind this cursor to its uninitialized state. Any options that are present on the cursor will
243276
* remain in effect. Iterating this cursor will cause new queries to be sent to the server, even

test/query.cursor.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
'use strict';
66

7+
const { once } = require('events');
78
const start = require('./common');
89

910
const assert = require('assert');
@@ -920,6 +921,34 @@ describe('QueryCursor', function() {
920921
assert.ok(cursor.cursor);
921922
assert.equal(driverCursor, cursor.cursor);
922923
});
924+
925+
it('handles destroy() (gh-14966)', async function() {
926+
db.deleteModel(/Test/);
927+
const TestModel = db.model('Test', mongoose.Schema({ name: String }));
928+
929+
const stream = await TestModel.find().cursor();
930+
await once(stream, 'cursor');
931+
assert.ok(!stream.cursor.closed);
932+
933+
stream.destroy();
934+
935+
await once(stream.cursor, 'close');
936+
assert.ok(stream.destroyed);
937+
assert.ok(stream.cursor.closed);
938+
});
939+
940+
it('handles destroy() before cursor is created (gh-14966)', async function() {
941+
db.deleteModel(/Test/);
942+
const TestModel = db.model('Test', mongoose.Schema({ name: String }));
943+
944+
const stream = await TestModel.find().cursor();
945+
assert.ok(!stream.cursor);
946+
stream.destroy();
947+
948+
await once(stream, 'cursor');
949+
assert.ok(stream.destroyed);
950+
assert.ok(stream.cursor.closed);
951+
});
923952
});
924953

925954
async function delay(ms) {

types/cursor.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ declare module 'mongoose' {
2626
*/
2727
close(): Promise<void>;
2828

29+
/**
30+
* Destroy this cursor, closing the underlying cursor. Will stop streaming
31+
* and subsequent calls to `next()` will error.
32+
*/
33+
destroy(): this;
34+
2935
/**
3036
* Rewind this cursor to its uninitialized state. Any options that are present on the cursor will
3137
* remain in effect. Iterating this cursor will cause new queries to be sent to the server, even

0 commit comments

Comments
 (0)