Skip to content

CSHARP-6029: Make ICoreServerSessionPool disposable and send endSessions on dispose#1995

Merged
sanych-sun merged 10 commits into
mongodb:mainfrom
sanych-sun:CSHARP-6029
May 25, 2026
Merged

CSHARP-6029: Make ICoreServerSessionPool disposable and send endSessions on dispose#1995
sanych-sun merged 10 commits into
mongodb:mainfrom
sanych-sun:CSHARP-6029

Conversation

@sanych-sun
Copy link
Copy Markdown
Member

No description provided.

Copilot AI review requested due to automatic review settings May 9, 2026 00:26
@sanych-sun sanych-sun requested a review from a team as a code owner May 9, 2026 00:26
@sanych-sun sanych-sun added the bug Fixes issues or unintended behavior. label May 9, 2026
@sanych-sun sanych-sun requested review from BorisDog and ajcvickers and removed request for ajcvickers May 9, 2026 00:26
@sanych-sun sanych-sun marked this pull request as draft May 9, 2026 00:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to ensure server-side sessions are explicitly ended when a client/cluster is disposed (via an endSessions command), and adjusts test event capture behavior to avoid capturing disposal-time commands.

Changes:

  • Added session-pool draining logic that issues endSessions for pooled server sessions during cluster disposal.
  • Introduced EventCapturer.Stop() and invoked it from the JSON-driven spec test runner.
  • Added a shared helper on Cluster to select an available server and trigger session-pool cleanup during disposal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/MongoDB.Driver.Tests/Specifications/Runner/MongoClientJsonDrivenTestRunnerBase.cs Stops event capture after test operations complete.
tests/MongoDB.Driver.TestHelpers/Core/EventCapturer.cs Adds a stop flag to disable capturing after disposal begins.
src/MongoDB.Driver/Core/Clusters/SingleServerCluster.cs Triggers session-pool cleanup before server removal on dispose.
src/MongoDB.Driver/Core/Clusters/MultiServerCluster.cs Triggers session-pool cleanup during cluster disposal.
src/MongoDB.Driver/Core/Clusters/Cluster.cs Adds ReleaseServerSessionPool() helper to drive cleanup.
src/MongoDB.Driver/Core/Bindings/ICoreServerSessionPool.cs Extends pool contract with ReleaseAll(IServer).
src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs Implements ReleaseAll to drain pooled sessions and send endSessions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/MongoDB.Driver.TestHelpers/Core/EventCapturer.cs
Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs
Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs Outdated
Comment on lines +116 to +120
}

sessionsBatch = _pool.GetRange(0, Math.Min(_pool.Count, 9999));
_pool.RemoveRange(0, sessionsBatch.Count);
}
Comment on lines +99 to +149
public void ReleaseAll(IServer server)
{
if (_pool.Count == 0)
{
return;
}

try
{
while (true)
{
List<ICoreServerSession> sessionsBatch;
lock (_lock)
{
if (_pool.Count == 0)
{
return;
}

sessionsBatch = _pool.GetRange(0, Math.Min(_pool.Count, 9999));
_pool.RemoveRange(0, sessionsBatch.Count);
}

var endSessionCommand = new BsonDocument("endSessions", new BsonArray(sessionsBatch.Select(s => s.Id)));
foreach (var session in sessionsBatch)
{
session.Dispose();
}

var operationContext = OperationContext.NoTimeout;
using var channel = server.GetChannel(operationContext);
channel.Command(
operationContext,
NoCoreSession.Instance,
ReadPreference.PrimaryPreferred,
DatabaseNamespace.Admin,
endSessionCommand,
null,
NoOpElementNameValidator.Instance,
null,
null,
CommandResponseHandling.Return,
BsonDocumentSerializer.Instance,
null);
}
}
catch
{
// Ignore any errors.
}
}
Comment on lines +20 to 25
internal interface ICoreServerSessionPool
{
internal interface ICoreServerSessionPool
{
ICoreServerSession AcquireSession();
void ReleaseSession(ICoreServerSession serverSession);
}
ICoreServerSession AcquireSession();
void ReleaseSession(ICoreServerSession serverSession);
void ReleaseAll(IServer server);
}
Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs
@sanych-sun sanych-sun marked this pull request as ready for review May 22, 2026 03:10
private readonly object _lock = new object();
private readonly List<ICoreServerSession> _pool = new List<ICoreServerSession>();
private readonly ILogger<LogCategories.Client> _logger;
private readonly ConcurrentStack<ICoreServerSession> _pool = new();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Accordingly to spec pool should be LIFO.

public void ReleaseSession(ICoreServerSession session)
{
lock (_lock)
ThrowIfDisposed();
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.

Is this be a behavioral breaking change? In the case server is disposed but the session/operation in flight is not, the exception might happen even for a successful operation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is, but if MongoClient/Cluster is being disposed - all resources associated with it should be cleaned up. Otherwise we might let cluster "resurrect" and have leaked resources.

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.

Yes the resources should be clean up.
But should the pool throw on the "after-cleanup release attempt"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If somebody disposed client/cluster and then suddenly started returning sessions to the pool: it means a) they disposed cluster too early b) the returned sessions might not be closed properly (if CloseAndDispose is already done).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

As it was discussed offline we will log an error for now to prevent any possible breaking change in the minor release.

}
else
{
Interlocked.Increment(ref _sessionsAcquired);
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.

nit: consider having a single place for incrementing for readability (single return)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs
Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs
{
_logger?.LogDebug(
"Closed server session pool for {clusterId} in {milliseconds}ms.",
_cluster.ClusterId, (Stopwatch.GetTimestamp() - timestamp) / (double)Stopwatch.Frequency * 1000);
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.

Add how many session where disposed (out of initial)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs
Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs
}
catch(Exception ex)
{
_logger?.LogError(ex, "Error closing server session pool for {clusterId}: {exception}", _cluster.ClusterId, ex);
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.

Do we need to pass ex as an message formatting argument as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's there, the last parameter.

Copy link
Copy Markdown
Contributor

@BorisDog BorisDog May 22, 2026

Choose a reason for hiding this comment

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

Yes, but do we need it twice?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, I see. Will remove.

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.

I still think passing ex.Message is redundant. Just ex as a first parameter is enough, as logger sink will handle all the formatting needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ok. Will remove.

@sanych-sun sanych-sun requested a review from BorisDog May 22, 2026 18:06
public void ReleaseSession(ICoreServerSession session)
{
lock (_lock)
ThrowIfDisposed();
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.

Yes the resources should be clean up.
But should the pool throw on the "after-cleanup release attempt"?

Comment thread src/MongoDB.Driver/Core/Bindings/CoreServerSessionPool.cs
Comment thread tests/MongoDB.Driver.Tests/Core/Bindings/CoreServerSessionPoolTests.cs Outdated
@sanych-sun sanych-sun requested a review from BorisDog May 22, 2026 21:03
Copy link
Copy Markdown
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

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

LGTM + minor comments.

}
catch(Exception ex)
{
_logger?.LogError(ex, "Error closing server session pool for {clusterId}: {exception}", _cluster.ClusterId, ex);
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.

I still think passing ex.Message is redundant. Just ex as a first parameter is enough, as logger sink will handle all the formatting needed.

{
lock (_lock)
if (_isDisposed)
{
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.

Dispose the session object?

@sanych-sun sanych-sun merged commit 528a7ad into mongodb:main May 25, 2026
33 checks passed
@sanych-sun sanych-sun deleted the CSHARP-6029 branch May 25, 2026 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Fixes issues or unintended behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants