-
Type: Improvement
-
Resolution: Unresolved
-
Priority: Major - P3
-
None
-
Affects Version/s: None
-
Component/s: Performance, Sessions
Current Behavior
In the constructor for AbstractCursor, when no session is provided as an option, we create a ClientSession and assign it to our private kSession property on the cursor.
This session is pinned to the cursor for the duration of the cursor's life. Only once the is fully iterated, we clean up the session.
Problem
If the cursor is never iterated, the session will never be destroyed and will remain active in memory in the `activeSessions` set on the MongoClient. This would be possible for any cursor command that does not iterate the cursor (FindCursor.count, for example).
I believe this impacts shell, since shell supports `cursor.count()`. My guess is that every shell find command returns a new cursor, so multiple cursor.counts() in a row would leak sessions.
Acceptance Criteria
Defer session creation until cursor initialization. This ensures that no session is allocated until iteration has begun. Currently, cursors must be manually cleaned up if partially iterated. This change would tie the lifecycle of the cursor's session to our supported behavior for our cursors.
Testing AC
Seek to add tests similar to MongoClient resource leak tests, create find cursors, don't iterate them and don't close them, assert memory usage does not grow unbounded if the cursors are allowed to go out of scope and the garbage collector has had a chance to run. Our goal is to try and detect arbitrary changes to the cursor class that could lead to leaks caused by saving resources to the parent collection/db/client.
Documentation
- Add documentation about cursor clean-up expectations to explain, count, and any other async cursor methods that do not relate to the cursor lifetime
- Document on find, aggregate, watch, listCollections, listSearchIndexes, listIndexes, and runCommandCursor when closing a cursor is and is not needed
Example Reproduction
import { MongoClient } from './src'; const client = new MongoClient(process.env.MONGODB_URI!); async function main() { const collection = client.db('foo').collection('bar'); const cursor = collection.find(); // do not iterate cursor await cursor.count(); // uncomment the following line to clean up all sessions on the client // await cursor.toArray(); } main() .then(() => { console.log(`number of active sessions on client: ${client.s.activeSessions.size}`); return client.close(); });
- is depended on by
-
MONGOSH-1622 mongosh crash due to an infinite loop
- Waiting (Blocked)
- is duplicated by
-
NODE-5729 If you don't use a cursor it seems to leak memory.
- Closed