Skip to content

CSHARP-5608: CSOT: Command Execution #1716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 2, 2025
Merged

Conversation

sanych-sun
Copy link
Member

No description provided.

@sanych-sun sanych-sun requested a review from a team as a code owner June 23, 2025 19:07
@sanych-sun sanych-sun requested review from papafe, rstam and adelinowona and removed request for a team and papafe June 23, 2025 19:07
@BorisDog BorisDog requested a review from Copilot June 24, 2025 21:52
Copy link

@Copilot 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 introduces explicit handling of server round-trip time ("RTT") by expanding SelectServer to return both a server and its RTT, and thread this RTT through various APIs and wire protocols to support operation timeouts and fail points.

  • Update SelectServer and SelectServerAsync to return (IServer server, TimeSpan roundTripTime).
  • Add serverRoundTripTime parameters to FailPoint.Configure, binding constructors, and CommandWireProtocol.
  • Inject RTT into command message sections for dynamic timeout calculation and update numerous tests to use the new overloads.

Reviewed Changes

Copilot reviewed 103 out of 103 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/MongoDB.Driver.Tests/UnifiedTestOperations/UnifiedTargetedFailPointOperation.cs Destructure (server, roundTripTime) and pass RTT into FailPoint.Configure
src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs Thread serverRoundTripTime into command creation and timeout logic
src/MongoDB.Driver/OperationContextExtensions.cs Add HasOperationTimeout and root context lookup
src/MongoDB.Driver/Core/WireProtocol/CommandWireProtocol.cs Update IWireProtocol methods to accept OperationContext and RTT
tests/MongoDB.Driver.Tests/Core/Bindings/SingleServerReadWriteBindingTests.cs Introduce RTT parameter in test constructors and add validation
Comments suppressed due to low confidence (3)

src/MongoDB.Driver/Core/WireProtocol/CommandWireProtocol.cs:88

  • [nitpick] The field _serverRoundTripTime is declared without XML doc. Add a brief comment explaining that this RTT is used for dynamic timeout calculations in command messages.
                messageEncoderSettings,

src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs:381

  • OperationContext does not define a RemainingTimeout property. You should compute the remaining time from the original timeout and elapsed time, or add a helper on OperationContext to expose the remaining timeout.
                var serverTimeout = operationContext.RemainingTimeout - _serverRoundTripTime;

src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs:630

  • RemainingTimeout is not a member of OperationContext. Consider replacing this check with your new helper (e.g., HasOperationTimeout) or implement a proper RemainingTimeout getter.
            if (operationContext.RemainingTimeout == Timeout.InfiniteTimeSpan)

Copy link
Contributor

@adelinowona adelinowona left a comment

Choose a reason for hiding this comment

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

No major comments from me, just minor issues/questions

EndServerSelection(expirableClusterDescription.ClusterDescription, selector, result.ServerDescription, stopwatch);
return result.Server;
EndServerSelection(expirableClusterDescription.ClusterDescription, selector, description, stopwatch);
return (server, description.AverageRoundTripTime);
Copy link
Member Author

Choose a reason for hiding this comment

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

Accordingly to CSOT spec we should use MinRoundTripTime here, but CSharp driver do not track it yet, we have only AverageRoundTripTime. We decided to go with the AverageRoundTripTime and replace it on the later stages of CSOT implementation. More over there is a chance that RTT will be removed from maxTimeMs calculation at all.
Find more details in the https://jira.mongodb.org/browse/CSHARP-5627

@sanych-sun sanych-sun requested a review from adelinowona June 25, 2025 17:40
Copy link
Contributor

@adelinowona adelinowona left a comment

Choose a reason for hiding this comment

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

LGTM

@sanych-sun sanych-sun requested a review from adelinowona June 25, 2025 23:38
@@ -61,10 +64,45 @@ public TimeSpan RemainingTimeout
}
}

[Obsolete("Do not use this property, unless it's needed to avoid breaking changes in public API")]
public CancellationToken CombinedCancellationToken
Copy link
Member Author

Choose a reason for hiding this comment

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

We have a number of use-cases when we cannot update the interfaces, because they are public (I've run into at least 2 cases like that: IStreamFactory and ISaslStep), so we have to squeeze both: cancellation token itself and timeout into linked cancellation token. Having such CombinedCancellationToken on operation context allow us to reuse instantiated CancellationTokenSources. Also I've marked the property as obsolete, so we easily can find all usages and replace with some interface changes in next major release.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is [Obsolete] really useful here?

When this property is removed the compiler will tell us where it is used.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, as I do not want to encourage more usages of the property.

@@ -94,7 +115,7 @@ public void WaitTask(Task task)
}

var timeout = RemainingTimeout;
if (timeout != System.Threading.Timeout.InfiniteTimeSpan && timeout < TimeSpan.Zero)
Copy link
Member Author

@sanych-sun sanych-sun Jun 25, 2025

Choose a reason for hiding this comment

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

RemainingTimeout returns TimeSpan.Zero if timed out now, so the check could be simplified.

{
var remainingTimeout = RemainingTimeout;
if (remainingTimeout == System.Threading.Timeout.InfiniteTimeSpan)
Copy link
Member Author

Choose a reason for hiding this comment

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

RemainingTimeout returns TimeSpan.Zero if timed out now, so the check could be simplified.

Copy link
Contributor

@adelinowona adelinowona left a comment

Choose a reason for hiding this comment

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

LGTM!

@@ -61,10 +64,45 @@ public TimeSpan RemainingTimeout
}
}

[Obsolete("Do not use this property, unless it's needed to avoid breaking changes in public API")]
public CancellationToken CombinedCancellationToken
Copy link
Contributor

Choose a reason for hiding this comment

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

Is [Obsolete] really useful here?

When this property is removed the compiler will tell us where it is used.

{
get { return _reference.Instance.Session; }
}
public TimeSpan RoundTripTime => _reference.Instance.RoundTripTime;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not needed if we use ISelectedServer interface.

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact this file probably needs NO changes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted most of the changes.

@@ -27,25 +27,21 @@ internal sealed class ChannelReadBinding : IReadBinding
private bool _disposed;
private readonly ReadPreference _readPreference;
private readonly IServer _server;
private readonly TimeSpan _roundTripTime;
Copy link
Contributor

Choose a reason for hiding this comment

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

This file needs NO changes if we use the new ISelectedServer interface.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted.

@@ -26,24 +26,20 @@ internal sealed class ChannelReadWriteBinding : IReadWriteBinding
private readonly IChannelHandle _channel;
private bool _disposed;
private readonly IServer _server;
private readonly TimeSpan _serverRoundTripTime;
Copy link
Contributor

Choose a reason for hiding this comment

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

This file needs NO changes if we use the new ISelectedServer interface.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted.

@@ -72,7 +65,6 @@ public void Dispose()
{
_reference.DecrementReferenceCount();
_disposed = true;
GC.SuppressFinalize(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you remove this line?

Copy link
Member Author

Choose a reason for hiding this comment

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

As far as I know there is no reason to call GC.SuppressFinalize on class without finalizer.

@@ -49,6 +49,7 @@ internal sealed class CommandUsingCommandMessageWireProtocol<TCommandResult> : I
private readonly CommandResponseHandling _responseHandling;
private readonly IBsonSerializer<TCommandResult> _resultSerializer;
private readonly ServerApi _serverApi;
private readonly TimeSpan _serverRoundTripTime;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should name be _minRoundTripTime?

Copy link
Member Author

Choose a reason for hiding this comment

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

I prefer _roundTripTime, I suppose the fact that this is suppose to be Min is kind of secondary here. The main thing here - this is round trip time that need to be used in the calculation. Especially now, when we do not support MinRoundTripTime yet.

@@ -369,6 +376,12 @@ private Type0CommandMessageSection<BsonDocument> CreateType0Section(ConnectionDe
}
}

if (operationContext.IsOperationTimeoutConfigured())
{
var serverTimeout = operationContext.RemainingTimeout - _serverRoundTripTime;
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to handle the possibility that serverTimeout will be negative here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep. Good catch!

@@ -45,6 +44,7 @@ internal sealed class CommandWireProtocol<TCommandResult> : IWireProtocol<TComma
private readonly CommandResponseHandling _responseHandling;
private readonly IBsonSerializer<TCommandResult> _resultSerializer;
private readonly ServerApi _serverApi;
private readonly TimeSpan _serverRoundTripTime;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should name be _minRoundTripTime?

@@ -32,6 +32,7 @@ public static IReadBindingHandle CreateReadBinding(IClusterInternal cluster, ICo
{
readBinding = new ChannelReadWriteBinding(
session.CurrentTransaction.PinnedServer,
session.CurrentTransaction.PinnedServerRoundTripTime,
Copy link
Contributor

Choose a reason for hiding this comment

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

This file needs NO changes if we use the new ISelectedServer interface.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted

@@ -1003,33 +1003,33 @@ private IReadBindingHandle GetSingleServerReadBinding(OperationContext operation
{
var readPreference = _options.ReadPreference ?? _database.Settings.ReadPreference;
var selector = new ReadPreferenceServerSelector(readPreference);
var server = _cluster.SelectServer(operationContext, selector);
var binding = new SingleServerReadBinding(server, readPreference, NoCoreSession.NewHandle());
var (server, serverRoundTripTime) = _cluster.SelectServer(operationContext, selector);
Copy link
Contributor

Choose a reason for hiding this comment

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

This file needs NO changes if we use the new ISelectedServer interface.

Copy link
Member Author

Choose a reason for hiding this comment

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

Reverted

Oleksandr Poliakov added 2 commits July 2, 2025 12:32
{
get { return _server; }
}
public IServer Server => _server;
Copy link
Contributor

Choose a reason for hiding this comment

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

This file now only has cosmetic changes.

Up to you whether they belong in this PR or not.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will keep

@@ -172,8 +192,12 @@ public void Invalidate(string reasonInvalidated, TopologyVersion responseTopolog

public abstract void RequestHeartbeat();

// protected methods
public void ReturnConnection(IConnectionHandle connection)
Copy link
Contributor

Choose a reason for hiding this comment

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

We're not actually returning the connection at all here. The connection parameter is ignored.

I suggest naming this method DecrementOutstandingOperationsCount.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

private readonly IConnectionHandle _connection;
private readonly IServer _server;
private readonly InterlockedInt32 _state;
private readonly bool _ownConnection;
Copy link
Contributor

Choose a reason for hiding this comment

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

Keep the original name for this field: _decrementOperationsCount

Copy link
Member Author

Choose a reason for hiding this comment

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

Done


IChannelHandle GetChannel(OperationContext operationContext);
Task<IChannelHandle> GetChannelAsync(OperationContext operationContext);
void HandleChannelException(IConnectionHandle connection, Exception exception);
void ReturnConnection(IConnectionHandle connection);
Copy link
Contributor

Choose a reason for hiding this comment

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

Name this method DecrementOutstandingOperationsCount.

A future refactoring might improve how we keep track of the operations count and might result in this method not being necessary in the future.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@sanych-sun sanych-sun requested a review from rstam July 2, 2025 22:40
Copy link
Contributor

@rstam rstam left a comment

Choose a reason for hiding this comment

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

LGTM

{
if (_decrementOperationsCount)
{
_server.DecrementOutstandingOperationsCount();
Copy link
Contributor

Choose a reason for hiding this comment

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

Github PR doesn't show what this line used to be because it was in a different file, but with the latest changes this line changed from:

Interlocked.Decrement(ref _server._outstandingOperationsCount);

to this.

Now it is much clearer that we are preserving existing behavior!

@sanych-sun sanych-sun merged commit f2382e6 into mongodb:main Jul 2, 2025
32 of 35 checks passed
@sanych-sun sanych-sun deleted the csharp5608 branch July 2, 2025 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants