This repository contains a reproduction case for a critical issue in the MongoDB Node.js driver.
When using the MongoDB Node.js driver, there's an issue in the rewind()
method of AbstractCursor
. The method attempts to call this.documents?.clear()
without checking if the clear()
method exists on the documents
object.
In certain scenarios, particularly when the MongoDB driver uses an optimization called emptyGetMore
to avoid unnecessary server calls, this.documents
is set to a simplified object literal that lacks the clear()
method:
CursorResponse.emptyGetMore = {
id: new bson_1.Long(0),
length: 0,
shift: () => null
// Note: no clear() method defined
};
This causes an error when rewind()
is called on cursors that have this optimization applied:
rewind() {
if (!this.initialized) {
return;
}
this.cursorId = null;
this.documents?.clear(); // <-- This line causes the error
this.isClosed = false;
this.isKilled = false;
this.initialized = false;
// ...
}
This issue can cause applications to crash when:
- Using cursor operations that internally call
rewind()
- Working with cursors that have been optimized using
emptyGetMore
- Attempting to reset or reuse cursors after they've been exhausted
The reproduction.js
script demonstrates this issue by:
- Creating a MongoDB cursor with a small batch size
- Consuming all documents to exhaust the cursor
- Simulating the emptyGetMore optimization by replacing the documents object with one that lacks a clear() method
- Attempting to rewind the cursor, which triggers the error
- Trying to reuse the cursor, which would normally work if rewind() succeeded
# Install dependencies
npm i
# Run the reproduction script
node reproduction.js
Expected output will show the error:
Rewind error: this.documents?.clear is not a function
Error name: TypeError
The issue can be fixed by modifying the rewind()
method to safely handle cases where documents
doesn't have a clear()
method. Here's the proposed fix:
rewind() {
if (!this.initialized) {
return;
}
this.cursorId = null;
// Check if documents exists and has a clear method before calling it
if (this.documents && typeof this.documents.clear === 'function') {
this.documents.clear();
} else {
// If documents is the emptyGetMore object or doesn't have the clear() method
// Simply set it to null to force reinitialization
this.documents = null;
}
this.isClosed = false;
this.isKilled = false;
this.initialized = false;
// ... rest of the method
}
- Safety: The solution checks if
clear()
exists before calling it - Functionality: Setting
documents
tonull
achieves the same goal asclear()
- it resets the cursor's document buffer - Compatibility: The solution maintains compatibility with both regular cursors and optimized
emptyGetMore
cursors - Performance: The solution doesn't introduce any significant overhead
This fix has already been implemented in Meteor's AsynchronousCursor._rewind()
method, which demonstrates that the approach works in a production environment.
The solution is fully backward compatible and requires no changes to the public API.