Fix leak of session objects on session close#875
Conversation
SessionObjects were kept alive in allObjects as tombstones with valid=false after their session closed, so every object ever created lingered until C_Finalize, holding its Mutex*. Long running clients saw unbounded memory growth dominated by mutex objects. Add refcounting to OSObject and release the store's reference in sessionClosed, allSessionsClosed, tokenLoggedOut, deleteObject, clearStore, and ~SessionObjectStore. Also erase from allObjects in these paths to avoid a double-free against the destructor.
📝 WalkthroughWalkthroughThis PR introduces intrusive atomic reference counting to ChangesIntrusive Reference Counting in Object Store
Sequence Diagram(s)sequenceDiagram
participant SessionObjectStore
participant storeMutex
participant ToRelease as toRelease set
participant OSObject
SessionObjectStore->>storeMutex: acquire lock
storeMutex-->>SessionObjectStore: locked
SessionObjectStore->>SessionObjectStore: collect matching objects
SessionObjectStore->>ToRelease: gather into toRelease
SessionObjectStore->>SessionObjectStore: erase from objects/allObjects
SessionObjectStore->>storeMutex: release lock
storeMutex-->>SessionObjectStore: unlocked
SessionObjectStore->>OSObject: release() each in toRelease
OSObject->>OSObject: decrement refCount
OSObject-->>SessionObjectStore: auto-delete on zero
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/object_store/SessionObjectStore.cpp`:
- Around line 143-146: getObjects(...) currently returns raw OSObject* pointers
copied under storeMutex but does not call acquire() on them, so subsequent
release() calls (e.g., in the code paths that drop references) can make callers
receive dangling pointers; fix by transferring real ownership: inside
getObjects() while holding storeMutex call acquire() on each OSObject* you plan
to return (or instead move returned pointers into a RAII holder that keeps an
owning reference) and require callers to call release() when done, and remove
the premature toRelease->release() patterns (or defer them until after callers
have released) so functions like getObjects(), any code paths that use
storeMutex, and the release() invocations near the referenced blocks all
preserve at least one live reference to each object until readers finish.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e0fb8be7-63de-48ab-b928-81ae28c6e293
📒 Files selected for processing (3)
src/lib/object_store/OSObject.hsrc/lib/object_store/SessionObjectStore.cppsrc/lib/object_store/test/SessionObjectStoreTests.cpp
| if (toRelease) | ||
| { | ||
| toRelease->release(); | ||
| } |
There was a problem hiding this comment.
These release() calls can turn getObjects() results into dangling pointers.
getObjects(...) still copies raw OSObject*s out under storeMutex and returns them without acquire(). Once any of these paths drops the store's last reference, a concurrent close/logout/delete/clear can destroy an object immediately after the reader unlocks, so the returned set may contain freed pointers. Please add a real ownership handoff here — e.g. retain each object before exposing it and require callers to release() later, or keep a separate owner alive until all readers are done.
Also applies to: 169-172, 193-196, 216-219, 231-234
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/object_store/SessionObjectStore.cpp` around lines 143 - 146,
getObjects(...) currently returns raw OSObject* pointers copied under storeMutex
but does not call acquire() on them, so subsequent release() calls (e.g., in the
code paths that drop references) can make callers receive dangling pointers; fix
by transferring real ownership: inside getObjects() while holding storeMutex
call acquire() on each OSObject* you plan to return (or instead move returned
pointers into a RAII holder that keeps an owning reference) and require callers
to call release() when done, and remove the premature toRelease->release()
patterns (or defer them until after callers have released) so functions like
getObjects(), any code paths that use storeMutex, and the release() invocations
near the referenced blocks all preserve at least one live reference to each
object until readers finish.
| for (std::set<OSObject*>::iterator i = objects.begin(); i != objects.end(); ++i) | ||
| { | ||
| for (int j = 0; j < 5; ++j) | ||
| { | ||
| if ((*i)->getAttribute(CKA_ID).getByteStringValue() == id[j]) | ||
| { |
There was a problem hiding this comment.
| for (std::set<OSObject*>::iterator i = objects.begin(); i != objects.end(); ++i) | |
| { | |
| for (int j = 0; j < 5; ++j) | |
| { | |
| if ((*i)->getAttribute(CKA_ID).getByteStringValue() == id[j]) | |
| { | |
| for (auto* obj : objects) | |
| { | |
| for (int j = 0; j < 5; ++j) | |
| { | |
| if (obj->getAttribute(CKA_ID).getByteStringValue() == id[j]) | |
| { |
SessionObjects were kept alive inallObjectsas tombstones withvalid=falseafter their session closed, so every object ever created lingered untilC_Finalize, holding itsMutex*. Long-running clients saw unbounded memory growth dominated by mutex objects.Add refcounting to
OSObjectand release the store's reference insessionClosed,allSessionsClosed,tokenLoggedOut,deleteObject,clearStore, and~SessionObjectStore. Also erase fromallObjectsin these paths to avoid a double-free against the destructor.This is the first step. Refcounting is introduced on the
OSObjectbase class even thoughSessionObjectis the only subclass wired throughrelease()here.ObjectFileandDBObjectkeep their existing direct-delete lifecycle for now. Later changes can extend the mechanism toHandleManagerand the token-object paths to close related stale-pointer races, but those are out of scope for the memory leak being fixed here.Summary by CodeRabbit
Bug Fixes
Refactor