Skip to content

Commit 3f74e5c

Browse files
author
rstam
committed
CSHARP-574: reduce possibility of lock contention and move matching logic out of ConnectedInstanceCollection and into ReplicaSetMongoServerProxy.
1 parent 8549cb7 commit 3f74e5c

File tree

3 files changed

+84
-90
lines changed

3 files changed

+84
-90
lines changed

Driver/Internal/ConnectedInstanceCollection.cs

Lines changed: 12 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ internal class ConnectedInstanceCollection
2929
{
3030
// private fields
3131
private readonly object _connectedInstancesLock = new object();
32-
private readonly Random _random = new Random();
33-
private readonly object _randomLock = new object();
3432

3533
private Dictionary<MongoServerInstance, LinkedListNode<InstanceWithPingTime>> _instanceLookup;
3634
private LinkedList<InstanceWithPingTime> _instances;
@@ -59,99 +57,29 @@ public void Clear()
5957
}
6058

6159
/// <summary>
62-
/// Gets the primary instance.
63-
/// </summary>
64-
/// <returns>The primary instance (or null if there is none).</returns>
65-
public MongoServerInstance GetPrimary()
66-
{
67-
lock (_connectedInstancesLock)
68-
{
69-
return _instances.Select(x => x.Instance).Where(i => i.IsPrimary).FirstOrDefault();
70-
}
71-
}
72-
73-
/// <summary>
74-
/// Gets a randomly selected matching instance.
60+
/// Gets a list of InstanceWithPingTimes.
7561
/// </summary>
76-
/// <param name="readPreference">The read preference must be matched.</param>
77-
/// <param name="secondaryAcceptableLatency">The maximum acceptable secondary latency.</param>
78-
/// <returns>A randomly selected matching instance.</returns>
79-
public MongoServerInstance GetRandomInstance(ReadPreference readPreference, TimeSpan secondaryAcceptableLatency)
62+
/// <returns>The list of InstanceWithPingTimes</returns>
63+
public List<InstanceWithPingTime> GetInstancesWithPingTime()
8064
{
8165
lock (_connectedInstancesLock)
8266
{
83-
var matchingInstances = new List<MongoServerInstance>();
84-
var maxPingTime = TimeSpan.MaxValue;
85-
86-
var tagSets = readPreference.TagSets ?? new ReplicaSetTagSet[] { new ReplicaSetTagSet() };
87-
foreach (var tagSet in tagSets)
88-
{
89-
foreach (var instanceWithPingTime in _instances)
90-
{
91-
if (instanceWithPingTime.CachedAveragePingTime > maxPingTime)
92-
{
93-
break; // the rest will exceed maxPingTime also
94-
}
95-
96-
var instance = instanceWithPingTime.Instance;
97-
if (instance.IsSecondary || instance.IsPrimary && readPreference.ReadPreferenceMode == ReadPreferenceMode.Nearest)
98-
{
99-
if (tagSet.MatchesInstance(instance))
100-
{
101-
matchingInstances.Add(instance);
102-
if (maxPingTime == TimeSpan.MaxValue)
103-
{
104-
maxPingTime = instanceWithPingTime.CachedAveragePingTime + secondaryAcceptableLatency;
105-
}
106-
}
107-
}
108-
}
109-
110-
if (matchingInstances.Count != 0)
111-
{
112-
var n = _random.Next(matchingInstances.Count);
113-
return matchingInstances[n]; // randomly selected matching instance
114-
}
115-
}
116-
117-
return null;
67+
// note: make copies of InstanceWithPingTime values because they can change after we return
68+
return _instances
69+
.Select(x => new InstanceWithPingTime { Instance = x.Instance, CachedAveragePingTime = x.CachedAveragePingTime })
70+
.ToList();
11871
}
11972
}
12073

12174
/// <summary>
122-
/// Gets a randomly selected matching instance.
75+
/// Gets the primary instance.
12376
/// </summary>
124-
/// <param name="secondaryAcceptableLatency">The maximum acceptable secondary latency.</param>
125-
/// <returns>A randomly selected matching instance.</returns>
126-
public MongoServerInstance GetRandomInstance(TimeSpan secondaryAcceptableLatency)
77+
/// <returns>The primary instance (or null if there is none).</returns>
78+
public MongoServerInstance GetPrimary()
12779
{
12880
lock (_connectedInstancesLock)
12981
{
130-
var matchingInstances = new List<MongoServerInstance>();
131-
var maxPingTime = TimeSpan.MaxValue;
132-
133-
foreach (var instanceWithPingTime in _instances)
134-
{
135-
if (instanceWithPingTime.CachedAveragePingTime > maxPingTime)
136-
{
137-
break; // the rest will exceed maxPingTime also
138-
}
139-
140-
var instance = instanceWithPingTime.Instance;
141-
matchingInstances.Add(instance);
142-
if (maxPingTime == TimeSpan.MaxValue)
143-
{
144-
maxPingTime = instanceWithPingTime.CachedAveragePingTime + secondaryAcceptableLatency;
145-
}
146-
}
147-
148-
if (matchingInstances.Count != 0)
149-
{
150-
var n = _random.Next(matchingInstances.Count);
151-
return matchingInstances[n]; // randomly selected matching instance
152-
}
153-
154-
return null;
82+
return _instances.Select(x => x.Instance).FirstOrDefault(i => i.IsPrimary);
15583
}
15684
}
15785

@@ -272,7 +200,7 @@ private void InstanceAveragePingTimeChanged(object sender, EventArgs e)
272200

273201
// When dealing with an always sorted linked list, we need to maintain a cached version of the ping time
274202
// to compare against because a MongoServerInstance's could change on it's own making the sorting of the list incorrect.
275-
private class InstanceWithPingTime
203+
internal class InstanceWithPingTime
276204
{
277205
public MongoServerInstance Instance;
278206
public TimeSpan CachedAveragePingTime;

Driver/Internal/ReplicaSetMongoServerProxy.cs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ namespace MongoDB.Driver.Internal
2727
internal sealed class ReplicaSetMongoServerProxy : MultipleInstanceMongoServerProxy
2828
{
2929
// private fields
30+
private readonly Random _random = new Random();
31+
private readonly object _randomLock = new object();
3032
private string _replicaSetName;
3133

3234
// constructors
@@ -85,14 +87,14 @@ protected override MongoServerInstance ChooseServerInstance(ConnectedInstanceCol
8587
}
8688
else
8789
{
88-
return connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
90+
return GetMatchingInstance(connectedInstances, readPreference, secondaryAcceptableLatency);
8991
}
9092

9193
case ReadPreferenceMode.Secondary:
92-
return connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
94+
return GetMatchingInstance(connectedInstances, readPreference, secondaryAcceptableLatency);
9395

9496
case ReadPreferenceMode.SecondaryPreferred:
95-
var secondary = connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
97+
var secondary = GetMatchingInstance(connectedInstances, readPreference, secondaryAcceptableLatency);
9698
if (secondary != null)
9799
{
98100
return secondary;
@@ -103,7 +105,7 @@ protected override MongoServerInstance ChooseServerInstance(ConnectedInstanceCol
103105
}
104106

105107
case ReadPreferenceMode.Nearest:
106-
return connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
108+
return GetMatchingInstance(connectedInstances, readPreference, secondaryAcceptableLatency);
107109

108110
default:
109111
throw new MongoInternalException("Invalid ReadPreferenceMode.");
@@ -198,6 +200,54 @@ protected override void ProcessConnectedInstanceStateChange(MongoServerInstance
198200
}
199201

200202
// private methods
203+
/// <summary>
204+
/// Gets a randomly selected matching instance.
205+
/// </summary>
206+
/// <param name="readPreference">The read preference that must be matched.</param>
207+
/// <param name="secondaryAcceptableLatency">The maximum acceptable secondary latency.</param>
208+
/// <returns>A randomly selected matching instance.</returns>
209+
public MongoServerInstance GetMatchingInstance(ConnectedInstanceCollection connectedInstances, ReadPreference readPreference, TimeSpan secondaryAcceptableLatency)
210+
{
211+
var instancesWithPingTime = connectedInstances.GetInstancesWithPingTime();
212+
213+
var tagSets = readPreference.TagSets ?? new ReplicaSetTagSet[] { new ReplicaSetTagSet() };
214+
foreach (var tagSet in tagSets)
215+
{
216+
var matchingInstances = new List<MongoServerInstance>();
217+
var maxPingTime = TimeSpan.MaxValue;
218+
219+
foreach (var instanceWithPingTime in instancesWithPingTime)
220+
{
221+
if (instanceWithPingTime.CachedAveragePingTime > maxPingTime)
222+
{
223+
break; // the rest will exceed maxPingTime also
224+
}
225+
226+
var instance = instanceWithPingTime.Instance;
227+
if (instance.IsSecondary || (readPreference.ReadPreferenceMode == ReadPreferenceMode.Nearest && instance.IsPrimary))
228+
{
229+
if (tagSet.MatchesInstance(instance))
230+
{
231+
matchingInstances.Add(instance);
232+
if (maxPingTime == TimeSpan.MaxValue)
233+
{
234+
maxPingTime = instanceWithPingTime.CachedAveragePingTime + secondaryAcceptableLatency;
235+
}
236+
}
237+
}
238+
}
239+
240+
// stop looking at tagSets if this one yielded any matching instances
241+
if (matchingInstances.Count != 0)
242+
{
243+
var index = _random.Next(matchingInstances.Count);
244+
return matchingInstances[index]; // randomly selected matching instance
245+
}
246+
}
247+
248+
return null;
249+
}
250+
201251
private void ProcessConnectedPrimaryStateChange(MongoServerInstance instance)
202252
{
203253
Interlocked.CompareExchange(ref _replicaSetName, instance.ReplicaSetInformation.Name, null);

Driver/Internal/ShardedMongoServerProxy.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ namespace MongoDB.Driver.Internal
2626
/// </summary>
2727
internal sealed class ShardedMongoServerProxy : MultipleInstanceMongoServerProxy
2828
{
29+
// private fields
30+
private readonly Random _random = new Random();
31+
private readonly object _randomLock = new object();
32+
2933
// constructors
3034
/// <summary>
3135
/// Initializes a new instance of the <see cref="ShardedMongoServerProxy"/> class.
@@ -55,8 +59,20 @@ public ShardedMongoServerProxy(MongoServer server, IEnumerable<MongoServerInstan
5559
/// <returns>A MongoServerInstance.</returns>
5660
protected override MongoServerInstance ChooseServerInstance(ConnectedInstanceCollection connectedInstances, ReadPreference readPreference)
5761
{
58-
var secondaryAcceptableLatency = TimeSpan.FromMilliseconds(15); // TODO: make configurable
59-
return connectedInstances.GetRandomInstance(secondaryAcceptableLatency);
62+
var instancesWithPingTime = connectedInstances.GetInstancesWithPingTime();
63+
if (instancesWithPingTime.Count == 0)
64+
{
65+
return null;
66+
}
67+
else
68+
{
69+
var secondaryAcceptableLatency = TimeSpan.FromMilliseconds(15); // TODO: make configurable
70+
var minPingTime = instancesWithPingTime[0].CachedAveragePingTime;
71+
var maxPingTime = minPingTime + secondaryAcceptableLatency;
72+
var n = instancesWithPingTime.Count(i => i.CachedAveragePingTime <= maxPingTime);
73+
var index = _random.Next(n);
74+
return instancesWithPingTime[index].Instance; // return random instance
75+
}
6076
}
6177

6278
/// <summary>

0 commit comments

Comments
 (0)