Skip to content

Commit 8549cb7

Browse files
author
rstam
committed
CSHARP-574: refactor the ReadPreference implementation and fix some issues.
1 parent 98a0129 commit 8549cb7

File tree

7 files changed

+146
-117
lines changed

7 files changed

+146
-117
lines changed

Driver/Core/ReadPreference.cs

Lines changed: 8 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ public class ReadPreference : IEquatable<ReadPreference>
6161
private static readonly ReadPreference __secondaryPreferred = new ReadPreference(ReadPreferenceMode.SecondaryPreferred).Freeze();
6262

6363
// private fields
64-
private readonly Random _random = new Random();
65-
private readonly object _randomLock = new object();
6664
private ReadPreferenceMode _readPreferenceMode;
6765
private List<ReplicaSetTagSet> _tagSets;
6866
private ReadOnlyCollection<ReplicaSetTagSet> _tagSetsReadOnly;
@@ -74,31 +72,26 @@ public class ReadPreference : IEquatable<ReadPreference>
7472
/// Initializes a new instance of the ReadPreference class.
7573
/// </summary>
7674
public ReadPreference()
75+
: this(ReadPreferenceMode.Primary)
7776
{
78-
_readPreferenceMode = ReadPreferenceMode.Primary;
7977
}
8078

8179
/// <summary>
8280
/// Initializes a new instance of the ReadPreference class.
8381
/// </summary>
8482
/// <param name="readPreference">A read preference</param>
8583
public ReadPreference(ReadPreference readPreference)
84+
: this(readPreference.ReadPreferenceMode, readPreference.TagSets)
8685
{
87-
_readPreferenceMode = readPreference._readPreferenceMode;
88-
if (readPreference._tagSets != null)
89-
{
90-
_tagSets = new List<ReplicaSetTagSet>(readPreference._tagSets);
91-
_tagSetsReadOnly = _tagSets.AsReadOnly();
92-
}
9386
}
9487

9588
/// <summary>
9689
/// Initializes a new instance of the ReadPreference class.
9790
/// </summary>
9891
/// <param name="readPreferenceMode">The read preference mode.</param>
9992
public ReadPreference(ReadPreferenceMode readPreferenceMode)
93+
: this(readPreferenceMode, null)
10094
{
101-
_readPreferenceMode = readPreferenceMode;
10295
}
10396

10497
/// <summary>
@@ -109,8 +102,11 @@ public ReadPreference(ReadPreferenceMode readPreferenceMode)
109102
public ReadPreference(ReadPreferenceMode readPreferenceMode, IEnumerable<ReplicaSetTagSet> tagSets)
110103
{
111104
_readPreferenceMode = readPreferenceMode;
112-
_tagSets = new List<ReplicaSetTagSet>(tagSets);
113-
_tagSetsReadOnly = _tagSets.AsReadOnly();
105+
if (tagSets != null)
106+
{
107+
_tagSets = new List<ReplicaSetTagSet>(tagSets);
108+
_tagSetsReadOnly = _tagSets.AsReadOnly();
109+
}
114110
}
115111

116112
// static properties
@@ -396,96 +392,6 @@ public override string ToString()
396392
}
397393

398394
// internal methods
399-
internal MongoServerInstance ChooseServerInstance(IEnumerable<MongoServerInstance> connectedInstancesByPingTime)
400-
{
401-
// tags are not evaluated for a primary
402-
if (_readPreferenceMode == ReadPreferenceMode.Primary || _readPreferenceMode == ReadPreferenceMode.PrimaryPreferred)
403-
{
404-
foreach (var instance in connectedInstancesByPingTime)
405-
{
406-
if (instance.IsPrimary)
407-
{
408-
return instance;
409-
}
410-
}
411-
if (_readPreferenceMode == ReadPreferenceMode.Primary)
412-
{
413-
return null;
414-
}
415-
}
416-
417-
List<MongoServerInstance> matchingInstances = new List<MongoServerInstance>();
418-
TimeSpan maxPingTime = TimeSpan.MaxValue;
419-
foreach (var instance in connectedInstancesByPingTime)
420-
{
421-
if (instance.AveragePingTime > maxPingTime)
422-
{
423-
break; // any subsequent instances will also exceed maxPingTime
424-
}
425-
if (MatchesInstance(instance))
426-
{
427-
if (maxPingTime == TimeSpan.MaxValue)
428-
{
429-
var secondaryAcceptableLatency = TimeSpan.FromMilliseconds(15);
430-
try
431-
{
432-
maxPingTime = instance.AveragePingTime + secondaryAcceptableLatency;
433-
}
434-
catch (OverflowException)
435-
{
436-
maxPingTime = TimeSpan.MaxValue;
437-
}
438-
}
439-
matchingInstances.Add(instance);
440-
}
441-
}
442-
443-
if (matchingInstances.Count == 0)
444-
{
445-
return null;
446-
}
447-
448-
if (_readPreferenceMode == ReadPreferenceMode.SecondaryPreferred)
449-
{
450-
MongoServerInstance primary = null;
451-
foreach (var instance in matchingInstances)
452-
{
453-
if (instance.IsPrimary)
454-
{
455-
primary = instance;
456-
break;
457-
}
458-
}
459-
460-
if (primary != null)
461-
{
462-
if (matchingInstances.Count == 1)
463-
{
464-
return primary;
465-
}
466-
else
467-
{
468-
matchingInstances.Remove(primary);
469-
}
470-
}
471-
}
472-
473-
switch (matchingInstances.Count)
474-
{
475-
case 0:
476-
return null;
477-
case 1:
478-
return matchingInstances[0];
479-
default:
480-
int randomIndex;
481-
lock (_randomLock)
482-
{
483-
randomIndex = _random.Next(matchingInstances.Count);
484-
}
485-
return matchingInstances[randomIndex]; // random load balancing
486-
}
487-
}
488-
489395
internal bool ToSlaveOk()
490396
{
491397
return _readPreferenceMode != ReadPreferenceMode.Primary;

Driver/Core/ReplicaSetTag.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ public class ReplicaSetTag : IEquatable<ReplicaSetTag>
3838
/// <param name="value">The value of the tag.</param>
3939
public ReplicaSetTag(string name, string value)
4040
{
41+
if (name == null)
42+
{
43+
throw new ArgumentNullException("name");
44+
}
45+
if (value == null)
46+
{
47+
throw new ArgumentNullException("value");
48+
}
4149
_name = name;
4250
_value = value;
4351
_hashCode = GetHashCodeHelper();

Driver/Internal/ConnectedInstanceCollection.cs

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ 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();
34+
3235
private Dictionary<MongoServerInstance, LinkedListNode<InstanceWithPingTime>> _instanceLookup;
3336
private LinkedList<InstanceWithPingTime> _instances;
3437

@@ -56,26 +59,100 @@ public void Clear()
5659
}
5760

5861
/// <summary>
59-
/// Chooses the server instance based on the read preference.
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.
6075
/// </summary>
61-
/// <param name="readPreference">The read preference.</param>
62-
/// <returns>A MongoServerInstance.</returns>
63-
public MongoServerInstance ChooseServerInstance(ReadPreference readPreference)
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)
6480
{
65-
List<MongoServerInstance> instances;
6681
lock (_connectedInstancesLock)
6782
{
68-
if (_instances.Count == 0)
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)
6988
{
70-
return null;
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+
}
71115
}
72116

73-
// We realize we are making extra instances of a list. It is to increase
74-
// concurrency related to ChooseServerInstance.
75-
instances = _instances.Select(x => x.Instance).ToList();
117+
return null;
76118
}
119+
}
77120

78-
return readPreference.ChooseServerInstance(instances);
121+
/// <summary>
122+
/// Gets a randomly selected matching instance.
123+
/// </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)
127+
{
128+
lock (_connectedInstancesLock)
129+
{
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;
155+
}
79156
}
80157

81158
/// <summary>

Driver/Internal/MultipleInstanceMongoServerProxy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public MongoServerBuildInfo BuildInfo
9090
{
9191
get
9292
{
93-
var instance = _connectedInstances.ChooseServerInstance(ReadPreference.Primary);
93+
var instance = ChooseServerInstance(ReadPreference.Primary);
9494
return instance == null
9595
? null
9696
: instance.BuildInfo;

Driver/Internal/ReplicaSetMongoServerProxy.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,44 @@ public string ReplicaSetName
7070
// protected methods
7171
protected override MongoServerInstance ChooseServerInstance(ConnectedInstanceCollection connectedInstances, ReadPreference readPreference)
7272
{
73-
return connectedInstances.ChooseServerInstance(readPreference);
73+
var secondaryAcceptableLatency = TimeSpan.FromMilliseconds(15); // TODO: make configurable
74+
75+
switch (readPreference.ReadPreferenceMode)
76+
{
77+
case ReadPreferenceMode.Primary:
78+
return connectedInstances.GetPrimary();
79+
80+
case ReadPreferenceMode.PrimaryPreferred:
81+
var primary = connectedInstances.GetPrimary();
82+
if (primary != null)
83+
{
84+
return primary;
85+
}
86+
else
87+
{
88+
return connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
89+
}
90+
91+
case ReadPreferenceMode.Secondary:
92+
return connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
93+
94+
case ReadPreferenceMode.SecondaryPreferred:
95+
var secondary = connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
96+
if (secondary != null)
97+
{
98+
return secondary;
99+
}
100+
else
101+
{
102+
return connectedInstances.GetPrimary();
103+
}
104+
105+
case ReadPreferenceMode.Nearest:
106+
return connectedInstances.GetRandomInstance(readPreference, secondaryAcceptableLatency);
107+
108+
default:
109+
throw new MongoInternalException("Invalid ReadPreferenceMode.");
110+
}
74111
}
75112

76113
/// <summary>

Driver/Internal/ShardedMongoServerProxy.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public ShardedMongoServerProxy(MongoServer server, IEnumerable<MongoServerInstan
5555
/// <returns>A MongoServerInstance.</returns>
5656
protected override MongoServerInstance ChooseServerInstance(ConnectedInstanceCollection connectedInstances, ReadPreference readPreference)
5757
{
58-
return connectedInstances.ChooseServerInstance(ReadPreference.Primary);
58+
var secondaryAcceptableLatency = TimeSpan.FromMilliseconds(15); // TODO: make configurable
59+
return connectedInstances.GetRandomInstance(secondaryAcceptableLatency);
5960
}
6061

6162
/// <summary>

DriverUnitTests/Core/MongoConnectionStringBuilderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ public void TestReadPreferenceSecondaryPreferred()
687687
}
688688

689689
[Test]
690-
public void TestReadPreferenceSecondaryNearest()
690+
public void TestReadPreferenceNearest()
691691
{
692692
var builder = new MongoConnectionStringBuilder() { Server = __localhost, ReadPreference = ReadPreference.Nearest };
693693
Assert.AreEqual(ReadPreferenceMode.Nearest, builder.ReadPreference.ReadPreferenceMode);

0 commit comments

Comments
 (0)