Skip to content

Commit 46165d6

Browse files
committed
[gh-10875] Use stream destroy method on close to prevent emit 'close' event twice
1 parent 4b8e0d1 commit 46165d6

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

lib/cursor/AggregationCursor.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,14 @@ AggregationCursor.prototype._read = function() {
9090
// on node >= 14 streams close automatically (gh-8834)
9191
const isNotClosedAutomatically = !_this.destroyed;
9292
if (isNotClosedAutomatically) {
93-
_this.emit('close');
93+
// call destroy method if exists to prevent emit twice 'close' by autoDestroy (gh-10876)
94+
// @see https://nodejs.org/api/stream.html#stream_readable_destroy_error
95+
// the 'close' is emited on destroy started with version 10
96+
if (_this.destroy && parseInt(process.versions.node.split('.')[0]) > 9) {
97+
_this.destroy();
98+
} else {
99+
_this.emit('close');
100+
}
94101
}
95102
}, 0);
96103
});

lib/cursor/QueryCursor.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,14 @@ QueryCursor.prototype._read = function() {
9999
// on node >= 14 streams close automatically (gh-8834)
100100
const isNotClosedAutomatically = !_this.destroyed;
101101
if (isNotClosedAutomatically) {
102-
_this.emit('close');
102+
// call destroy method if exists to prevent emit twice 'close' by autoDestroy (gh-10876)
103+
// @see https://nodejs.org/api/stream.html#stream_readable_destroy_error
104+
// the close is emited on destroy started with version 10
105+
if (_this.destroy && parseInt(process.versions.node.split('.')[0]) > 9) {
106+
_this.destroy();
107+
} else {
108+
_this.emit('close');
109+
}
103110
}
104111
}, 0);
105112
});

test/query.cursor.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,48 @@ describe('QueryCursor', function() {
644644
}, 20);
645645
});
646646

647+
it('closing query cursor emits `close` event only once with stream pause/resume (gh-10876)', function(done) {
648+
const User = db.model('User', new Schema({ name: String }));
649+
650+
User.create({ name: 'First' }, { name: 'Second' })
651+
.then(() => {
652+
const cursor = User.find().cursor();
653+
cursor.on('data', () => {
654+
cursor.pause();
655+
setTimeout(() => cursor.resume(), 50);
656+
});
657+
658+
let closeEventTriggeredCount = 0;
659+
cursor.on('close', () => closeEventTriggeredCount++);
660+
setTimeout(() => {
661+
assert.equal(closeEventTriggeredCount, 1);
662+
done();
663+
}, 200);
664+
});
665+
});
666+
667+
it('closing aggregation cursor emits `close` event only once with stream pause/resume (gh-10876)', function(done) {
668+
const User = db.model('User', new Schema({ name: String }));
669+
670+
User.create({ name: 'First' }, { name: 'Second' })
671+
.then(() => {
672+
const cursor = User.aggregate([{ $match: {} }]).cursor().exec();
673+
cursor.on('data', () => {
674+
cursor.pause();
675+
setTimeout(() => cursor.resume(), 50);
676+
});
677+
678+
let closeEventTriggeredCount = 0;
679+
cursor.on('close', () => closeEventTriggeredCount++);
680+
681+
682+
setTimeout(() => {
683+
assert.equal(closeEventTriggeredCount, 1);
684+
done();
685+
}, 200);
686+
});
687+
});
688+
647689
it('passes document index as the second argument for query cursor (gh-8972)', function() {
648690
return co(function *() {
649691
const User = db.model('User', Schema({ order: Number }));

0 commit comments

Comments
 (0)