Skip to content

Commit f78ad53

Browse files
authored
CSHARP-5496: Reduce locks contention on server selection and connection checkout (#1617)
* CSHARP-5496: Reduce locks contention on server selection and connection checkout * PR * Fix formatting * pr --------- Co-authored-by: Oleksandr Poliakov <[email protected]>
1 parent 924ec63 commit f78ad53

File tree

3 files changed

+32
-33
lines changed

3 files changed

+32
-33
lines changed

src/MongoDB.Driver/Core/Clusters/Cluster.cs

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ internal abstract class Cluster : IClusterInternal
4646
private readonly TimeSpan _minHeartbeatInterval = __minHeartbeatIntervalDefault;
4747
private readonly IClusterClock _clusterClock = new ClusterClock();
4848
private readonly ClusterId _clusterId;
49-
private ClusterDescription _description;
50-
private TaskCompletionSource<bool> _descriptionChangedTaskCompletionSource;
51-
private readonly object _descriptionLock = new object();
49+
private ClusterDescriptionChangeSource _descriptionWithChangedTaskCompletionSource;
5250
private readonly LatencyLimitingServerSelector _latencyLimitingServerSelector;
5351
protected readonly EventLogger<LogCategories.SDAM> _clusterEventLogger;
5452
protected readonly EventLogger<LogCategories.ServerSelection> _serverSelectionEventLogger;
@@ -70,10 +68,8 @@ protected Cluster(ClusterSettings settings, IClusterableServerFactory serverFact
7068
Ensure.IsNotNull(eventSubscriber, nameof(eventSubscriber));
7169
_state = new InterlockedInt32(State.Initial);
7270
_rapidHeartbeatTimerCallbackState = new InterlockedInt32(RapidHeartbeatTimerCallbackState.NotRunning);
73-
7471
_clusterId = new ClusterId();
75-
_description = ClusterDescription.CreateInitial(_clusterId, _settings.DirectConnection);
76-
_descriptionChangedTaskCompletionSource = new TaskCompletionSource<bool>();
72+
_descriptionWithChangedTaskCompletionSource = new (ClusterDescription.CreateInitial(_clusterId, _settings.DirectConnection));
7773
_latencyLimitingServerSelector = new LatencyLimitingServerSelector(settings.LocalThreshold);
7874

7975
_rapidHeartbeatTimer = new Timer(RapidHeartbeatTimerCallback, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
@@ -97,10 +93,7 @@ public ClusterDescription Description
9793
{
9894
get
9995
{
100-
lock (_descriptionLock)
101-
{
102-
return _description;
103-
}
96+
return _descriptionWithChangedTaskCompletionSource.ClusterDescription;
10497
}
10598
}
10699

@@ -134,7 +127,7 @@ protected virtual void Dispose(bool disposing)
134127

135128
var newClusterDescription = new ClusterDescription(
136129
_clusterId,
137-
_description.DirectConnection,
130+
_descriptionWithChangedTaskCompletionSource.ClusterDescription.DirectConnection,
138131
dnsMonitorException: null,
139132
ClusterType.Unknown,
140133
Enumerable.Empty<ServerDescription>());
@@ -293,22 +286,11 @@ public ICoreSessionHandle StartSession(CoreSessionOptions options)
293286

294287
protected void UpdateClusterDescription(ClusterDescription newClusterDescription, bool shouldClusterDescriptionChangedEventBePublished = true)
295288
{
296-
ClusterDescription oldClusterDescription = null;
297-
TaskCompletionSource<bool> oldDescriptionChangedTaskCompletionSource = null;
289+
var oldClusterDescription = Interlocked.Exchange(ref _descriptionWithChangedTaskCompletionSource, new(newClusterDescription));
298290

299-
lock (_descriptionLock)
300-
{
301-
oldClusterDescription = _description;
302-
_description = newClusterDescription;
291+
OnDescriptionChanged(oldClusterDescription.ClusterDescription, newClusterDescription, shouldClusterDescriptionChangedEventBePublished);
303292

304-
oldDescriptionChangedTaskCompletionSource = _descriptionChangedTaskCompletionSource;
305-
_descriptionChangedTaskCompletionSource = new TaskCompletionSource<bool>();
306-
}
307-
308-
OnDescriptionChanged(oldClusterDescription, newClusterDescription, shouldClusterDescriptionChangedEventBePublished);
309-
310-
// TODO: use RunContinuationsAsynchronously instead once we require a new enough .NET Framework
311-
Task.Run(() => oldDescriptionChangedTaskCompletionSource.TrySetResult(true));
293+
oldClusterDescription.TrySetChanged();
312294
}
313295

314296
private string BuildTimeoutExceptionMessage(TimeSpan timeout, IServerSelector selector, ClusterDescription clusterDescription)
@@ -363,6 +345,25 @@ private void ThrowTimeoutException(IServerSelector selector, ClusterDescription
363345
}
364346

365347
// nested classes
348+
internal sealed class ClusterDescriptionChangeSource
349+
{
350+
private readonly TaskCompletionSource<bool> _changedTaskCompletionSource;
351+
private readonly ClusterDescription _clusterDescription;
352+
353+
public ClusterDescriptionChangeSource(ClusterDescription clusterDescription)
354+
{
355+
_changedTaskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
356+
_clusterDescription = clusterDescription;
357+
}
358+
359+
public ClusterDescription ClusterDescription => _clusterDescription;
360+
361+
public Task Changed => _changedTaskCompletionSource.Task;
362+
363+
public bool TrySetChanged()
364+
=> _changedTaskCompletionSource.TrySetResult(true);
365+
}
366+
366367
private class SelectServerHelper : IDisposable
367368
{
368369
private readonly Cluster _cluster;
@@ -380,7 +381,7 @@ public SelectServerHelper(Cluster cluster, IServerSelector selector)
380381
{
381382
_cluster = cluster;
382383

383-
_connectedServers = new List<IClusterableServer>(_cluster._description?.Servers?.Count ?? 1);
384+
_connectedServers = new List<IClusterableServer>(_cluster._descriptionWithChangedTaskCompletionSource.ClusterDescription?.Servers?.Count ?? 1);
384385
_connectedServerDescriptions = new List<ServerDescription>(_connectedServers.Count);
385386
_operationCountServerSelector = new OperationsCountServerSelector(_connectedServers);
386387

@@ -429,11 +430,9 @@ public void HandleException(Exception exception)
429430

430431
public IServer SelectServer()
431432
{
432-
lock (_cluster._descriptionLock)
433-
{
434-
_descriptionChangedTask = _cluster._descriptionChangedTaskCompletionSource.Task;
435-
_description = _cluster._description;
436-
}
433+
var clusterDescription = _cluster._descriptionWithChangedTaskCompletionSource;
434+
_descriptionChangedTask = clusterDescription.Changed;
435+
_description = clusterDescription.ClusterDescription;
437436

438437
if (!_serverSelectionWaitQueueEntered)
439438
{

tests/MongoDB.Driver.Tests/Core/Jira/CSharp3173Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ private IServerSelector CreateWritableServerAndEndPointSelector(EndPoint endPoin
289289
private void ForceClusterId(MultiServerCluster cluster, ClusterId clusterId)
290290
{
291291
Reflector.SetFieldValue(cluster, "_clusterId", clusterId);
292-
Reflector.SetFieldValue(cluster, "_description", ClusterDescription.CreateInitial(clusterId, __directConnection));
292+
Reflector.SetFieldValue(cluster, "_descriptionWithChangedTaskCompletionSource", new Cluster.ClusterDescriptionChangeSource(ClusterDescription.CreateInitial(clusterId, __directConnection)));
293293
}
294294

295295
private void SetupServerMonitorConnection(

tests/MongoDB.Driver.Tests/Core/Jira/CSharp3302Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ private IServerSelector CreateWritableServerAndEndPointSelector(EndPoint endPoin
270270
private void ForceClusterId(MultiServerCluster cluster, ClusterId clusterId)
271271
{
272272
Reflector.SetFieldValue(cluster, "_clusterId", clusterId);
273-
Reflector.SetFieldValue(cluster, "_description", ClusterDescription.CreateInitial(clusterId, __directConnection));
273+
Reflector.SetFieldValue(cluster, "_descriptionWithChangedTaskCompletionSource", new Cluster.ClusterDescriptionChangeSource(ClusterDescription.CreateInitial(clusterId, __directConnection)));
274274
}
275275

276276
private void SetupServerMonitorConnection(

0 commit comments

Comments
 (0)