Skip to content

Fix leak of session objects on session close#875

Open
bukka wants to merge 1 commit into
softhsm:mainfrom
bukka:session-close-object-free
Open

Fix leak of session objects on session close#875
bukka wants to merge 1 commit into
softhsm:mainfrom
bukka:session-close-object-free

Conversation

@bukka
Copy link
Copy Markdown
Member

@bukka bukka commented May 17, 2026

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.

This is the first step. Refcounting is introduced on the OSObject base class even though SessionObject is the only subclass wired through release() here. ObjectFile and DBObject keep their existing direct-delete lifecycle for now. Later changes can extend the mechanism to HandleManager and 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

    • Improved thread safety and stability in session object lifecycle management.
  • Refactor

    • Enhanced object store to prevent race conditions and improve resource handling during concurrent operations.

Review Change Stack

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.
@bukka bukka requested a review from a team as a code owner May 17, 2026 17:19
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

This PR introduces intrusive atomic reference counting to OSObject and refactors SessionObjectStore to defer object release outside lock-protected sections, eliminating delete-while-locked patterns and improving concurrency safety. Tests are updated to validate object lifecycle through attribute tracking rather than pointer identity.

Changes

Intrusive Reference Counting in Object Store

Layer / File(s) Summary
Atomic reference counting in OSObject
src/lib/object_store/OSObject.h
OSObject gains a default constructor initializing refCount to 1, public acquire() and release() methods with noexcept and memory-order parameters, a private std::atomic<int> refCount member, and a defaulted destructor; release() decrements the count and deletes this when zero.
Lock-free cleanup in SessionObjectStore
src/lib/object_store/SessionObjectStore.cpp
Destructor, deleteObject(), sessionClosed(), allSessionsClosed(), tokenLoggedOut(), and clearStore() are refactored to collect objects for release into a local set under lock, then call release() on each object after the lock is released.
Test refactoring to attribute-based object tracking
src/lib/object_store/test/SessionObjectStoreTests.cpp
testMultiSession is restructured to track object presence via CKA_ID attributes in a boolean array rather than direct pointer membership; test is organized into phases aligned with session closures (session 2, session 1, session 7), with memset used to reset the presence array between queries.

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
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A counter counts with grace,
Locks release with perfect space,
Objects fly when refCount drops,
Cleanup outside where logic stops—
Tests now see the IDs shine!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix leak of session objects on session close' directly and concisely describes the main change: addressing a memory leak where session objects persisted after session closure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 02cebd5 and 119b27b.

📒 Files selected for processing (3)
  • src/lib/object_store/OSObject.h
  • src/lib/object_store/SessionObjectStore.cpp
  • src/lib/object_store/test/SessionObjectStoreTests.cpp

Comment on lines +143 to 146
if (toRelease)
{
toRelease->release();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

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.

Comment on lines +244 to +249
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])
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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])
{

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants