Skip to content

CSHARP-3671: Better wait queue timeout errors for load balanced clusters. #579

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 4 commits into from
Jul 23, 2021

Conversation

DmitryLukyanov
Copy link
Contributor

No description provided.

@jyemin jyemin requested a review from rstam July 14, 2021 17:28
@DmitryLukyanov DmitryLukyanov force-pushed the csharp3671 branch 2 times, most recently from f7f627e to 0f5b393 Compare July 15, 2021 00:30
var numberOfCheckOutsForCursor = pool._checkedOutTracker.GetCheckedOutNumber(CheckedOutReason.Cursor);
var numberOfCheckOutsForTransaction = pool._checkedOutTracker.GetCheckedOutNumber(CheckedOutReason.Transaction);

var message = numberOfCheckOutsForCursor == 0 && numberOfCheckOutsForTransaction == 0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this logic is copied from java, if we have no checkouts for transaction or cursor, we use old variant of exception message

Copy link
Contributor Author

@DmitryLukyanov DmitryLukyanov Jul 15, 2021

Choose a reason for hiding this comment

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

also, this logic can be simplified if in the current master version we will leave only one timeout exception message (we have two that are a bit different), it looks like a single timeout error message is how java behaves.
But I will be ok with the current way also. cc: @rstam

@@ -104,14 +104,14 @@ public override IAsyncCursor<TResult> Aggregate<TResult>(IClientSessionHandle se
var forkedSession = session.Fork();
var deferredCursor = new DeferredAsyncCursor<TResult>(
() => forkedSession.Dispose(),
ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, ct),
ct => ExecuteReadOperationAsync(forkedSession, findOperation, ReadPreference.Primary, ct));
ct => ExecuteReadOperation(forkedSession, findOperation, ReadPreference.Primary, doesInitiateCursor: true, ct),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

in theory we can avoid such changes via analyzing an operation type (which is like IReadOperation<IAsyncCursor<TResult>>), but I didn't find a way to do it without reflection which can reduce performance

@@ -63,7 +63,7 @@ public async Task<OperationResult> ExecuteAsync(CancellationToken cancellationTo
? await _database.ListCollectionsAsync(_options, cancellationToken)
: await _database.ListCollectionsAsync(_session, _options, cancellationToken);

_ = cursor.ToListAsync(cancellationToken);
_ = await cursor.ToListAsync(cancellationToken);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this and below are changes from a different PR

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.

Please re-request my review when you've added the new commit you said you were working on.

@DmitryLukyanov DmitryLukyanov requested a review from rstam July 21, 2021 16:42
@DmitryLukyanov
Copy link
Contributor Author

The latest 2 commits contains simplifying of the logic in this PR:

  1. (Simplifying.: the second from the end): de9ab50
    EG: https://evergreen.mongodb.com/version/60f843552fbabe15604d13da
    Revert most of the changes in this PR, but didn't change the pinning logic itself. So, I changed only how we set a reason.
  2. (Simplifying (v2) : the last commit): ee358f6. This PR continues simplifying started in the first commit. With this change we don't need to replace channel/channelSource in the retry logic anymore. This change assumes that any operation won't require acquiring connection more than initial once. So, for the case when we only create a cursor, the actual channel pinning will happen only before creating cursor itself (previously I pinned connection right after creating)
    EG: https://evergreen.mongodb.com/version/60f84c70c9ec444dca52e795

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.

I like this approach a lot. It makes sense.

Don't be dismayed by the number of requested changes. They are almost all requests to rename something.

Requested changes prototyped here:

https://github.com/rstam/mongo-csharp-driver/tree/csharp3671-0722

@@ -29,6 +29,7 @@ namespace MongoDB.Driver.Core.ConnectionPools
internal sealed partial class ExclusiveConnectionPool : IConnectionPool
{
// fields
private readonly CheckedOutTracker _checkedOutTracker;
Copy link
Contributor

Choose a reason for hiding this comment

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

private readonly CheckoutReasonCounter _checkoutReasonCounter;

@@ -71,6 +72,7 @@ internal sealed partial class ExclusiveConnectionPool : IConnectionPool
_connectionFactory = Ensure.IsNotNull(connectionFactory, nameof(connectionFactory));
Ensure.IsNotNull(eventSubscriber, nameof(eventSubscriber));

_checkedOutTracker = new CheckedOutTracker();
Copy link
Contributor

Choose a reason for hiding this comment

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

_checkoutReasonCounter = new CheckoutReasonCounter();

@@ -385,6 +387,8 @@ private void ReleaseConnection(PooledConnection connection)
_checkedInConnectionEventHandler(new ConnectionPoolCheckedInConnectionEvent(connection.ConnectionId, TimeSpan.Zero, EventContext.OperationId));
}

_checkedOutTracker.CheckInIfNotNull(connection.CheckedOutReason);
Copy link
Contributor

Choose a reason for hiding this comment

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

_checkoutReasonCounter.Decrement(connection.CheckoutReason);

@@ -39,6 +39,23 @@ private static class State
public const int Disposed = 2;
}

private static Exception CreateEffectiveTimeoutException(string defaultExceptionMessage, ExclusiveConnectionPool pool, Stopwatch stopwatch)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be an instance method of the ExclusiveConnectionPool:

// private methods
private Exception CreateTimeoutException(Stopwatch stopwatch, string message)
{
    var checkoutsForCursorCount = _checkoutReasonCounter.GetCheckoutsCount(CheckoutReason.Cursor);
    var checkoutsForTransactionCount = _checkoutReasonCounter.GetCheckoutsCount(CheckoutReason.Transaction);

    // only use the expanded message format when connected to a load balancer
    if (checkoutsForCursorCount != 0 || checkoutsForTransactionCount != 0)
    {
        var maxPoolSize = _settings.MaxConnections;
        var availableConnectionsCount = AvailableCount;
        var checkoutsCount = maxPoolSize - availableConnectionsCount;
        var checkoutsForOtherCount = checkoutsCount - checkoutsForCursorCount - checkoutsForTransactionCount;

        message =
            $"Timed out after {stopwatch.ElapsedMilliseconds}ms waiting for a connection from the connection pool. " +
            $"maxPoolSize: {maxPoolSize}, " +
            $"connections in use by cursors: {checkoutsForCursorCount}, " +
            $"connections in use by transactions: {checkoutsForTransactionCount}, " +
            $"connections in use by other operations: {checkoutsForOtherCount}.";
    }

    return new TimeoutException(message);
}

Copy link
Contributor

@rstam rstam Jul 23, 2021

Choose a reason for hiding this comment

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

Also... move to after line 33 (this is not a nested class).

Note: yet another reason why this should not be a partial class. This is an instance method of the ExclusiveConnectionPool class. According to the split of this class into two files (which I disagree with) this instance method should be in the ExclusiveConnectionPool.cs file, BUT... it is only used in this file.

So where should it go?

Yet another example of why this is not how partial classes should be used.

}
}

public void CheckInIfNotNull(CheckedOutReason? reason)
Copy link
Contributor

Choose a reason for hiding this comment

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

public void Decrement(CheckoutReason? reason)

// should not be reached
throw new InvalidOperationException($"Unsupported checked out reason {reason}.");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace 71-88 with:

switch (reason)
{
    case null:
        break;
    case CheckoutReason.Cursor:
        Interlocked.Decrement(ref _cursorCheckoutsCount);
        break;
    case CheckoutReason.Transaction:
        Interlocked.Decrement(ref _transactionCheckoutsCount);
        break;
    default:
        throw new InvalidOperationException($"Invalid checkout reason {reason}.");
}


// pinning channel
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mean?

// pin to channel

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I meant that this is pinning implementation(to cursor) requested in the ticket: Update connection pinning logic for LB mode, I will just remove this comment

{
session.CurrentTransaction.PinChannel(channel.Fork());
session.CurrentTransaction.PinnedServer = server;
trackedConnection.SetPinningCheckoutReasonIfNotAlreadySet(CheckedOutReason.Transaction);
Copy link
Contributor

Choose a reason for hiding this comment

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

if (channel.Connection is ICheckoutReasonTracker checkoutReasonTracker)
{
    checkoutReasonTracker.SetCheckoutReasonIfNotAlreadySet(CheckoutReason.Transaction);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor Author

@DmitryLukyanov DmitryLukyanov left a comment

Choose a reason for hiding this comment

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

ready for review


namespace MongoDB.Driver.Core.ConnectionPools
{
internal enum CheckedOutReason
Copy link
Contributor Author

Choose a reason for hiding this comment

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

previously it shown a difference between Event*ing* and Event*ed*, but now it's useless anymore, done


// pinning channel
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I meant that this is pinning implementation(to cursor) requested in the ticket: Update connection pinning logic for LB mode, I will just remove this comment

{
session.CurrentTransaction.PinChannel(channel.Fork());
session.CurrentTransaction.PinnedServer = server;
trackedConnection.SetPinningCheckoutReasonIfNotAlreadySet(CheckedOutReason.Transaction);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

break;
default:
// should not be reached
throw new InvalidOperationException($"Unsupported checked out reason {reason}.");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,91 @@
/*Copyright 2021 - present MongoDB Inc.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

{
internal enum CheckedOutReason
{
NotSet,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep, this was a bug, originally I set reason for any checkout operations, now it's not and I missed this case

Transaction
}

internal interface ITrackedPinningReason
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@DmitryLukyanov DmitryLukyanov requested a review from rstam July 23, 2021 17:10
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 (looks great to me)

@DmitryLukyanov DmitryLukyanov merged commit 882e798 into mongodb:master Jul 23, 2021
BorisDog pushed a commit to BorisDog/mongo-csharp-driver that referenced this pull request Jul 26, 2021
BorisDog pushed a commit to BorisDog/mongo-csharp-driver that referenced this pull request Aug 4, 2021
…ers. (mongodb#579)

CSHARP-3671: Better wait queue timeout errors for load balanced clusters.
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