Skip to content

Commit 912d890

Browse files
committed
fix(NODE-6255): cursor throws exhausted errors after explicit close
1 parent 54efb7d commit 912d890

File tree

2 files changed

+29
-5
lines changed

2 files changed

+29
-5
lines changed

src/cursor/abstract_cursor.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ export abstract class AbstractCursor<
146146
/** @internal */
147147
private isClosed: boolean;
148148
/** @internal */
149+
private isForceClosed: boolean;
150+
/** @internal */
149151
private isKilled: boolean;
150152
/** @internal */
151153
protected readonly cursorOptions: InternalAbstractCursorOptions;
@@ -169,6 +171,7 @@ export abstract class AbstractCursor<
169171
this.cursorId = null;
170172
this.initialized = false;
171173
this.isClosed = false;
174+
this.isForceClosed = false;
172175
this.isKilled = false;
173176
this.cursorOptions = {
174177
readPreference:
@@ -369,7 +372,7 @@ export abstract class AbstractCursor<
369372
}
370373

371374
async hasNext(): Promise<boolean> {
372-
if (this.cursorId === Long.ZERO) {
375+
if (this.cursorId === Long.ZERO || this.isForceClosed) {
373376
return false;
374377
}
375378

@@ -385,7 +388,7 @@ export abstract class AbstractCursor<
385388

386389
/** Get the next available document from the cursor, returns null if no more documents are available. */
387390
async next(): Promise<TSchema | null> {
388-
if (this.cursorId === Long.ZERO) {
391+
if (this.cursorId === Long.ZERO || this.isForceClosed) {
389392
throw new MongoCursorExhaustedError();
390393
}
391394

@@ -405,7 +408,7 @@ export abstract class AbstractCursor<
405408
* Try to get the next available document from the cursor or `null` if an empty batch is returned
406409
*/
407410
async tryNext(): Promise<TSchema | null> {
408-
if (this.cursorId === Long.ZERO) {
411+
if (this.cursorId === Long.ZERO || this.isForceClosed) {
409412
throw new MongoCursorExhaustedError();
410413
}
411414

@@ -447,6 +450,12 @@ export abstract class AbstractCursor<
447450
}
448451

449452
async close(): Promise<void> {
453+
// We flag that an explicit call to close the cursor has happened, so no matter
454+
// what the current state is it can no longer be used. This is do to areas in the
455+
// cursor that are setting the isClosed flag or cursor id to zero without going
456+
// through this path.
457+
this.isForceClosed = true;
458+
this.documents?.clear();
450459
await this.cleanup();
451460
}
452461

test/integration/node-specific/abstract_cursor.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,12 +348,27 @@ describe('class AbstractCursor', function () {
348348
});
349349
});
350350

351+
context('when the cursor is closed', function () {
352+
context('when calling next()', function () {
353+
it('raises a cursor exhausted error', async function () {
354+
cursor = client.db().collection('test').find({});
355+
await cursor.next();
356+
await cursor.close();
357+
const error = await cursor.next().catch(error => error);
358+
expect(error).to.be.instanceOf(MongoCursorExhaustedError);
359+
expect(cursor.id.isZero()).to.be.true;
360+
expect(cursor).to.have.property('closed', true);
361+
expect(cursor).to.have.property('killed', false);
362+
});
363+
});
364+
});
365+
351366
describe('when some documents have been iterated and the cursor is closed', () => {
352-
it('has a zero id and is not closed and is killed', async function () {
367+
it('has a zero id and is closed and is killed', async function () {
353368
cursor = client.db().collection('test').find({}, { batchSize: 2 });
354369
await cursor.next();
355370
await cursor.close();
356-
expect(cursor).to.have.property('closed', false);
371+
expect(cursor).to.have.property('closed', true);
357372
expect(cursor).to.have.property('killed', true);
358373
expect(cursor.id.isZero()).to.be.true;
359374
const error = await cursor.next().catch(error => error);

0 commit comments

Comments
 (0)