Skip to content

Commit eecbb01

Browse files
committed
CSHARP-3754: Implemented srvMaxHosts for both initial seedlist and SRV polling. (#666)
1 parent 5cdfe04 commit eecbb01

File tree

66 files changed

+1316
-194
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1316
-194
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public LoadBalancedCluster(
8686

8787
Ensure.IsEqualTo(settings.EndPoints.Count, 1, nameof(settings.EndPoints.Count));
8888
Ensure.IsNull(settings.ReplicaSetName, nameof(settings.ReplicaSetName));
89+
Ensure.That(settings.SrvMaxHosts == 0, "srvMaxHosts cannot be used with load balanced mode.");
8990

9091
_clusterClock = new ClusterClock();
9192
_clusterId = new ClusterId();

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -576,18 +576,24 @@ void IDnsMonitoringCluster.ProcessDnsResults(List<DnsEndPoint> dnsEndPoints)
576576
var newClusterDescription = oldClusterDescription;
577577
var currentEndPoints = oldClusterDescription.Servers.Select(serverDescription => serverDescription.EndPoint).ToList();
578578

579-
var endPointsToAdd = dnsEndPoints.Where(endPoint => !currentEndPoints.Contains(endPoint));
580-
foreach (var endPoint in endPointsToAdd)
581-
{
582-
newClusterDescription = EnsureServer(newClusterDescription, endPoint, newServers);
583-
}
584-
585579
var endPointsToRemove = currentEndPoints.Where(endPoint => !dnsEndPoints.Contains(endPoint));
586580
foreach (var endPoint in endPointsToRemove)
587581
{
588582
newClusterDescription = RemoveServer(newClusterDescription, endPoint, "Server no longer appears in the DNS SRV records.");
589583
}
590584

585+
var endPointsToAdd = dnsEndPoints.Where(endPoint => !currentEndPoints.Contains(endPoint)).ToList();
586+
var srvMaxHosts = Settings.SrvMaxHosts;
587+
if (srvMaxHosts > 0)
588+
{
589+
FisherYatesShuffle.Shuffle(endPointsToAdd);
590+
endPointsToAdd = endPointsToAdd.Take(srvMaxHosts - currentEndPoints.Count + endPointsToRemove.Count()).ToList();
591+
}
592+
foreach (var endPoint in endPointsToAdd)
593+
{
594+
newClusterDescription = EnsureServer(newClusterDescription, endPoint, newServers);
595+
}
596+
591597
newClusterDescription = newClusterDescription.WithDnsMonitorException(null);
592598
UpdateClusterDescription(newClusterDescription);
593599
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ internal sealed class SingleServerCluster : Cluster
4646
internal SingleServerCluster(ClusterSettings settings, IClusterableServerFactory serverFactory, IEventSubscriber eventSubscriber)
4747
: base(settings, serverFactory, eventSubscriber)
4848
{
49+
Ensure.That(settings.SrvMaxHosts == 0, "srvMaxHosts cannot be used with a single server cluster.");
4950
Ensure.IsEqualTo(settings.EndPoints.Count, 1, "settings.EndPoints.Count");
5051
_replicaSetName = settings.ReplicaSetName; // can be null
5152

src/MongoDB.Driver.Core/Core/Configuration/ClusterSettings.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public class ClusterSettings
5050
private readonly ConnectionStringScheme _scheme;
5151
private readonly ServerApi _serverApi;
5252
private readonly TimeSpan _serverSelectionTimeout;
53+
private readonly int _srvMaxHosts;
5354
private readonly IServerSelector _preServerSelector;
5455
private readonly IServerSelector _postServerSelector;
5556

@@ -72,6 +73,7 @@ public class ClusterSettings
7273
/// <param name="postServerSelector">The post server selector.</param>
7374
/// <param name="schemaMap">The schema map.</param>
7475
/// <param name="scheme">The connection string scheme.</param>
76+
/// <param name="srvMaxHosts">Limits the number of SRV records used to populate the seedlist during initial discovery, as well as the number of additional hosts that may be added during SRV polling.</param>
7577
public ClusterSettings(
7678
#pragma warning disable CS0618 // Type or member is obsolete
7779
Optional<ClusterConnectionMode> connectionMode = default(Optional<ClusterConnectionMode>),
@@ -89,25 +91,27 @@ public ClusterSettings(
8991
Optional<IServerSelector> preServerSelector = default(Optional<IServerSelector>),
9092
Optional<IServerSelector> postServerSelector = default(Optional<IServerSelector>),
9193
Optional<IReadOnlyDictionary<string, BsonDocument>> schemaMap = default(Optional<IReadOnlyDictionary<string, BsonDocument>>),
92-
Optional<ConnectionStringScheme> scheme = default(Optional<ConnectionStringScheme>))
94+
Optional<ConnectionStringScheme> scheme = default(Optional<ConnectionStringScheme>),
95+
Optional<int> srvMaxHosts = default)
9396
{
9497
#pragma warning disable CS0618 // Type or member is obsolete
9598
_connectionMode = connectionMode.WithDefault(ClusterConnectionMode.Automatic);
9699
_connectionModeSwitch = connectionModeSwitch.WithDefault(ConnectionModeSwitch.NotSet);
97100
#pragma warning restore CS0618 // Type or member is obsolete
98101
_directConnection = directConnection.WithDefault(null);
99-
_endPoints = Ensure.IsNotNull(endPoints.WithDefault(__defaultEndPoints), "endPoints").ToList();
102+
_endPoints = Ensure.IsNotNull(endPoints.WithDefault(__defaultEndPoints), nameof(endPoints)).ToList();
100103
_kmsProviders = kmsProviders.WithDefault(null);
101104
_loadBalanced = loadBalanced.WithDefault(false);
102-
_localThreshold = Ensure.IsInfiniteOrGreaterThanOrEqualToZero(localThreshold.WithDefault(TimeSpan.FromMilliseconds(15)), "localThreshold");
103-
_maxServerSelectionWaitQueueSize = Ensure.IsGreaterThanOrEqualToZero(maxServerSelectionWaitQueueSize.WithDefault(500), "maxServerSelectionWaitQueueSize");
105+
_localThreshold = Ensure.IsInfiniteOrGreaterThanOrEqualToZero(localThreshold.WithDefault(TimeSpan.FromMilliseconds(15)), nameof(localThreshold));
106+
_maxServerSelectionWaitQueueSize = Ensure.IsGreaterThanOrEqualToZero(maxServerSelectionWaitQueueSize.WithDefault(500), nameof(maxServerSelectionWaitQueueSize));
104107
_replicaSetName = replicaSetName.WithDefault(null);
105108
_serverApi = serverApi.WithDefault(null);
106-
_serverSelectionTimeout = Ensure.IsGreaterThanOrEqualToZero(serverSelectionTimeout.WithDefault(TimeSpan.FromSeconds(30)), "serverSelectionTimeout");
109+
_serverSelectionTimeout = Ensure.IsGreaterThanOrEqualToZero(serverSelectionTimeout.WithDefault(TimeSpan.FromSeconds(30)), nameof(serverSelectionTimeout));
107110
_preServerSelector = preServerSelector.WithDefault(null);
108111
_postServerSelector = postServerSelector.WithDefault(null);
109112
_scheme = scheme.WithDefault(ConnectionStringScheme.MongoDB);
110113
_schemaMap = schemaMap.WithDefault(null);
114+
_srvMaxHosts = Ensure.IsGreaterThanOrEqualToZero(srvMaxHosts.WithDefault(0), nameof(srvMaxHosts));
111115

112116
ClusterConnectionModeHelper.EnsureConnectionModeValuesAreValid(_connectionMode, _connectionModeSwitch, _directConnection);
113117
}
@@ -265,6 +269,13 @@ public TimeSpan ServerSelectionTimeout
265269
get { return _serverSelectionTimeout; }
266270
}
267271

272+
/// <summary>
273+
/// Limits the number of SRV records used to populate the seedlist
274+
/// during initial discovery, as well as the number of additional hosts
275+
/// that may be added during SRV polling.
276+
/// </summary>
277+
public int SrvMaxHosts => _srvMaxHosts;
278+
268279
/// <summary>
269280
/// Gets the pre server selector.
270281
/// </summary>
@@ -306,6 +317,7 @@ public IServerSelector PostServerSelector
306317
/// <param name="postServerSelector">The post server selector.</param>
307318
/// <param name="schemaMap">The schema map.</param>
308319
/// <param name="scheme">The connection string scheme.</param>
320+
/// <param name="srvMaxHosts">Limits the number of SRV records used to populate the seedlist during initial discovery, as well as the number of additional hosts that may be added during SRV polling.</param>
309321
/// <returns>A new ClusterSettings instance.</returns>
310322
public ClusterSettings With(
311323
#pragma warning disable CS0618 // Type or member is obsolete
@@ -324,7 +336,8 @@ public ClusterSettings With(
324336
Optional<IServerSelector> preServerSelector = default(Optional<IServerSelector>),
325337
Optional<IServerSelector> postServerSelector = default(Optional<IServerSelector>),
326338
Optional<IReadOnlyDictionary<string, BsonDocument>> schemaMap = default(Optional<IReadOnlyDictionary<string, BsonDocument>>),
327-
Optional<ConnectionStringScheme> scheme = default(Optional<ConnectionStringScheme>))
339+
Optional<ConnectionStringScheme> scheme = default(Optional<ConnectionStringScheme>),
340+
Optional<int> srvMaxHosts = default)
328341
{
329342
return new ClusterSettings(
330343
connectionMode: connectionMode.WithDefault(_connectionMode),
@@ -341,7 +354,8 @@ public ClusterSettings With(
341354
preServerSelector: Optional.Create(preServerSelector.WithDefault(_preServerSelector)),
342355
postServerSelector: Optional.Create(postServerSelector.WithDefault(_postServerSelector)),
343356
schemaMap: Optional.Create(schemaMap.WithDefault(_schemaMap)),
344-
scheme: scheme.WithDefault(_scheme));
357+
scheme: scheme.WithDefault(_scheme),
358+
srvMaxHosts: srvMaxHosts.WithDefault(_srvMaxHosts));
345359
}
346360

347361
// internal methods

src/MongoDB.Driver.Core/Core/Configuration/ConnectionString.cs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
using MongoDB.Driver.Core.Clusters;
2727
using MongoDB.Driver.Core.Compression;
2828
using MongoDB.Driver.Core.Misc;
29-
using MongoDB.Shared;
3029

3130
namespace MongoDB.Driver.Core.Configuration
3231
{
@@ -80,7 +79,8 @@ public sealed class ConnectionString
8079
private TimeSpan? _heartbeatTimeout;
8180
private IReadOnlyList<EndPoint> _hosts;
8281
private bool? _ipv6;
83-
private bool _isResolved;
82+
private readonly bool _isInternalRepresentation;
83+
private readonly bool _isResolved;
8484
private bool? _journal;
8585
private bool _loadBalanced;
8686
private TimeSpan? _localThreshold;
@@ -100,6 +100,7 @@ public sealed class ConnectionString
100100
private ConnectionStringScheme _scheme;
101101
private TimeSpan? _serverSelectionTimeout;
102102
private TimeSpan? _socketTimeout;
103+
private int? _srvMaxHosts;
103104
private bool? _tls;
104105
private bool? _tlsDisableCertificateRevocationCheck;
105106
private bool? _tlsInsecure;
@@ -116,13 +117,14 @@ public sealed class ConnectionString
116117
/// Initializes a new instance of the <see cref="ConnectionString" /> class.
117118
/// </summary>
118119
/// <param name="connectionString">The connection string.</param>
119-
public ConnectionString(string connectionString) : this(connectionString, DnsClientWrapper.Instance)
120+
public ConnectionString(string connectionString) : this(connectionString, false, DnsClientWrapper.Instance)
120121
{
121122
}
122123

123-
internal ConnectionString(string connectionString, IDnsResolver dnsResolver)
124+
internal ConnectionString(string connectionString, bool isInternalRepresentation, IDnsResolver dnsResolver)
124125
{
125126
_originalConnectionString = Ensure.IsNotNull(connectionString, nameof(connectionString));
127+
_isInternalRepresentation = isInternalRepresentation;
126128

127129
_allOptions = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
128130
_unknownOptions = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
@@ -140,7 +142,7 @@ internal ConnectionString(string connectionString, IDnsResolver dnsResolver)
140142
/// <param name="connectionString">The connection string.</param>
141143
/// <param name="isResolved">Whether the connection string is resolved.</param>
142144
internal ConnectionString(string connectionString, bool isResolved)
143-
: this(connectionString)
145+
: this(connectionString, true, DnsClientWrapper.Instance)
144146
{
145147
if (!isResolved && _scheme != ConnectionStringScheme.MongoDBPlusSrv)
146148
{
@@ -294,7 +296,7 @@ public TimeSpan? HeartbeatTimeout
294296
/// </summary>
295297
public IReadOnlyList<EndPoint> Hosts
296298
{
297-
get { return _hosts; }
299+
get { return _srvMaxHosts > 0 ? _hosts.Take(_srvMaxHosts.Value).ToList() : _hosts; }
298300
}
299301

300302
/// <summary>
@@ -466,6 +468,13 @@ public TimeSpan? SocketTimeout
466468
get { return _socketTimeout; }
467469
}
468470

471+
/// <summary>
472+
/// Limits the number of SRV records used to populate the seedlist
473+
/// during initial discovery, as well as the number of additional hosts
474+
/// that may be added during SRV polling.
475+
/// </summary>
476+
public int? SrvMaxHosts => _srvMaxHosts;
477+
469478
/// <summary>
470479
/// Gets whether to use SSL.
471480
/// </summary>
@@ -685,7 +694,7 @@ private ConnectionString BuildResolvedConnectionString(ConnectionStringScheme re
685694
connectionString += string.Join(",", resolvedHosts) + "/";
686695
if (_databaseName != null)
687696
{
688-
connectionString += Uri.EscapeDataString(_databaseName) + "/";
697+
connectionString += Uri.EscapeDataString(_databaseName);
689698
}
690699

691700
// remove any option from the resolved options that was specified locally
@@ -899,6 +908,16 @@ private void Parse()
899908
throw new MongoConfigurationException("Direct connect cannot be used with multiple host names.");
900909
}
901910

911+
if (!_isInternalRepresentation && _srvMaxHosts > 0 && _scheme != ConnectionStringScheme.MongoDBPlusSrv)
912+
{
913+
throw new MongoConfigurationException("srvMaxHosts can only be used with the mongodb+srv scheme.");
914+
}
915+
916+
if (_replicaSet != null && _srvMaxHosts > 0)
917+
{
918+
throw new MongoConfigurationException("Specifying srvMaxHosts when connecting to a replica set is invalid.");
919+
}
920+
902921
if (_loadBalanced)
903922
{
904923
if (_hosts.Count > 1)
@@ -911,6 +930,11 @@ private void Parse()
911930
throw new MongoConfigurationException("ReplicaSetName cannot be used with load balanced mode.");
912931
}
913932

933+
if (_srvMaxHosts > 0)
934+
{
935+
throw new MongoConfigurationException("srvMaxHosts cannot be used with load balanced mode.");
936+
}
937+
914938
if (IsDirectConnection())
915939
{
916940
throw new MongoConfigurationException("Load balanced mode cannot be used with direct connection.");
@@ -1081,6 +1105,14 @@ private void ParseOption(string name, string value)
10811105
case "sockettimeoutms":
10821106
_socketTimeout = ParseTimeSpan(name, value);
10831107
break;
1108+
case "srvmaxhosts":
1109+
var srvMaxHostsValue = ParseInt32(name, value);
1110+
if (srvMaxHostsValue < 0)
1111+
{
1112+
throw new MongoConfigurationException("srvMaxHosts must be greater than or equal to 0.");
1113+
}
1114+
_srvMaxHosts = srvMaxHostsValue;
1115+
break;
10841116
case "ssl": // Obsolete
10851117
case "tls":
10861118
var tlsValue = ParseBoolean(name, value);
@@ -1319,6 +1351,11 @@ private List<string> GetHostsFromSrvRecords(IEnumerable<SrvRecord> srvRecords)
13191351
hosts.Add(h + ":" + srvRecord.EndPoint.Port);
13201352
}
13211353

1354+
if (_srvMaxHosts > 0)
1355+
{
1356+
FisherYatesShuffle.Shuffle(hosts);
1357+
}
1358+
13221359
return hosts;
13231360
}
13241361

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Collections.Generic;
17+
18+
namespace MongoDB.Driver.Core.Misc
19+
{
20+
internal static class FisherYatesShuffle
21+
{
22+
public static void Shuffle<T>(IList<T> list)
23+
{
24+
Ensure.IsNotNull(list, nameof(list));
25+
26+
for (var i = list.Count - 1; i > 0; i--)
27+
{
28+
var j = ThreadStaticRandom.Next(i + 1);
29+
if (i != j)
30+
{
31+
(list[j], list[i]) = (list[i], list[j]);
32+
}
33+
}
34+
}
35+
}
36+
}

src/MongoDB.Driver.Legacy/MongoServerSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@ internal ClusterKey ToClusterKey()
12191219
_servers.ToList(),
12201220
_serverSelectionTimeout,
12211221
_socketTimeout,
1222+
srvMaxHosts: 0, // not supported for legacy
12221223
_sslSettings,
12231224
_useTls,
12241225
_waitQueueSize,

src/MongoDB.Driver/ClusterKey.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ internal class ClusterKey
6060
private readonly IReadOnlyList<MongoServerAddress> _servers;
6161
private readonly TimeSpan _serverSelectionTimeout;
6262
private readonly TimeSpan _socketTimeout;
63+
private readonly int _srvMaxHosts;
6364
private readonly SslSettings _sslSettings;
6465
private readonly bool _useTls;
6566
private readonly int _waitQueueSize;
@@ -99,6 +100,7 @@ public ClusterKey(
99100
IReadOnlyList<MongoServerAddress> servers,
100101
TimeSpan serverSelectionTimeout,
101102
TimeSpan socketTimeout,
103+
int srvMaxHosts,
102104
SslSettings sslSettings,
103105
bool useTls,
104106
int waitQueueSize,
@@ -136,6 +138,7 @@ public ClusterKey(
136138
_servers = servers;
137139
_serverSelectionTimeout = serverSelectionTimeout;
138140
_socketTimeout = socketTimeout;
141+
_srvMaxHosts = srvMaxHosts;
139142
_sslSettings = sslSettings;
140143
_useTls = useTls;
141144
_waitQueueSize = waitQueueSize;
@@ -199,6 +202,7 @@ public bool? DirectConnection
199202
public IReadOnlyList<MongoServerAddress> Servers { get { return _servers; } }
200203
public TimeSpan ServerSelectionTimeout { get { return _serverSelectionTimeout; } }
201204
public TimeSpan SocketTimeout { get { return _socketTimeout; } }
205+
public int SrvMaxHosts { get { return _srvMaxHosts; } }
202206
public SslSettings SslSettings { get { return _sslSettings; } }
203207
public bool UseTls => _useTls;
204208
public int WaitQueueSize { get { return _waitQueueSize; } }
@@ -253,6 +257,7 @@ public override bool Equals(object obj)
253257
_servers.SequenceEqual(rhs._servers) &&
254258
_serverSelectionTimeout == rhs._serverSelectionTimeout &&
255259
_socketTimeout == rhs._socketTimeout &&
260+
_srvMaxHosts == rhs._srvMaxHosts &&
256261
object.Equals(_sslSettings, rhs._sslSettings) &&
257262
_useTls == rhs._useTls &&
258263
_waitQueueSize == rhs._waitQueueSize &&

0 commit comments

Comments
 (0)