Skip to content

Commit

Permalink
fix(NODE-6255): cursor throws exhausted errors after explicit close
Browse files Browse the repository at this point in the history
  • Loading branch information
durran committed Aug 7, 2024
1 parent 54efb7d commit 912d890
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 5 deletions.
15 changes: 12 additions & 3 deletions src/cursor/abstract_cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ export abstract class AbstractCursor<
/** @internal */
private isClosed: boolean;
/** @internal */
private isForceClosed: boolean;
/** @internal */
private isKilled: boolean;
/** @internal */
protected readonly cursorOptions: InternalAbstractCursorOptions;
Expand All @@ -169,6 +171,7 @@ export abstract class AbstractCursor<
this.cursorId = null;
this.initialized = false;
this.isClosed = false;
this.isForceClosed = false;
this.isKilled = false;
this.cursorOptions = {
readPreference:
Expand Down Expand Up @@ -369,7 +372,7 @@ export abstract class AbstractCursor<
}

async hasNext(): Promise<boolean> {
if (this.cursorId === Long.ZERO) {
if (this.cursorId === Long.ZERO || this.isForceClosed) {
return false;
}

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

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

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

Expand Down Expand Up @@ -447,6 +450,12 @@ export abstract class AbstractCursor<
}

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

Expand Down
19 changes: 17 additions & 2 deletions test/integration/node-specific/abstract_cursor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,27 @@ describe('class AbstractCursor', function () {
});
});

context('when the cursor is closed', function () {
context('when calling next()', function () {
it('raises a cursor exhausted error', async function () {
cursor = client.db().collection('test').find({});
await cursor.next();
await cursor.close();
const error = await cursor.next().catch(error => error);
expect(error).to.be.instanceOf(MongoCursorExhaustedError);
expect(cursor.id.isZero()).to.be.true;
expect(cursor).to.have.property('closed', true);
expect(cursor).to.have.property('killed', false);
});
});
});

describe('when some documents have been iterated and the cursor is closed', () => {
it('has a zero id and is not closed and is killed', async function () {
it('has a zero id and is closed and is killed', async function () {
cursor = client.db().collection('test').find({}, { batchSize: 2 });
await cursor.next();
await cursor.close();
expect(cursor).to.have.property('closed', false);
expect(cursor).to.have.property('closed', true);
expect(cursor).to.have.property('killed', true);
expect(cursor.id.isZero()).to.be.true;
const error = await cursor.next().catch(error => error);
Expand Down

0 comments on commit 912d890

Please sign in to comment.