Skip to content

Commit b1560c5

Browse files
committed
CSHARP-1888: ServerMonitor should use HeartbeatTimeout for socket read/write timeouts.
1 parent 07f716c commit b1560c5

File tree

12 files changed

+244
-41
lines changed

12 files changed

+244
-41
lines changed

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

Lines changed: 89 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
using System.IO;
1919
using System.Linq;
2020
using System.Reflection;
21+
using System.Threading;
22+
using MongoDB.Driver.Core.Authentication;
2123
using MongoDB.Driver.Core.Clusters;
2224
using MongoDB.Driver.Core.ConnectionPools;
2325
using MongoDB.Driver.Core.Connections;
@@ -63,48 +65,14 @@ public ClusterBuilder()
6365
_eventAggregator = new EventAggregator();
6466
}
6567

66-
// methods
68+
// public methods
6769
/// <summary>
6870
/// Builds the cluster.
6971
/// </summary>
7072
/// <returns>A cluster.</returns>
7173
public ICluster BuildCluster()
7274
{
73-
IStreamFactory streamFactory = new TcpStreamFactory(_tcpStreamSettings);
74-
if (_sslStreamSettings != null)
75-
{
76-
streamFactory = new SslStreamFactory(_sslStreamSettings, streamFactory);
77-
}
78-
79-
streamFactory = _streamFactoryWrapper(streamFactory);
80-
81-
var connectionFactory = new BinaryConnectionFactory(
82-
_connectionSettings,
83-
streamFactory,
84-
_eventAggregator);
85-
86-
var connectionPoolFactory = new ExclusiveConnectionPoolFactory(
87-
_connectionPoolSettings,
88-
connectionFactory,
89-
_eventAggregator);
90-
91-
var serverMonitorFactory = new ServerMonitorFactory(
92-
_serverSettings,
93-
connectionFactory,
94-
_eventAggregator);
95-
96-
var serverFactory = new ServerFactory(
97-
_clusterSettings.ConnectionMode,
98-
_serverSettings,
99-
connectionPoolFactory,
100-
serverMonitorFactory,
101-
_eventAggregator);
102-
103-
var clusterFactory = new ClusterFactory(
104-
_clusterSettings,
105-
serverFactory,
106-
_eventAggregator);
107-
75+
var clusterFactory = CreateClusterFactory();
10876
return clusterFactory.CreateCluster();
10977
}
11078

@@ -242,5 +210,90 @@ public ClusterBuilder Subscribe(IEventSubscriber subscriber)
242210
_eventAggregator.Subscribe(subscriber);
243211
return this;
244212
}
213+
214+
// private methods
215+
private IClusterFactory CreateClusterFactory()
216+
{
217+
var serverFactory = CreateServerFactory();
218+
219+
return new ClusterFactory(
220+
_clusterSettings,
221+
serverFactory,
222+
_eventAggregator);
223+
}
224+
225+
private IConnectionPoolFactory CreateConnectionPoolFactory()
226+
{
227+
var streamFactory = CreateTcpStreamFactory(_tcpStreamSettings);
228+
229+
var connectionFactory = new BinaryConnectionFactory(
230+
_connectionSettings,
231+
streamFactory,
232+
_eventAggregator);
233+
234+
return new ExclusiveConnectionPoolFactory(
235+
_connectionPoolSettings,
236+
connectionFactory,
237+
_eventAggregator);
238+
}
239+
240+
private ServerFactory CreateServerFactory()
241+
{
242+
var connectionPoolFactory = CreateConnectionPoolFactory();
243+
var serverMonitorFactory = CreateServerMonitorFactory();
244+
245+
return new ServerFactory(
246+
_clusterSettings.ConnectionMode,
247+
_serverSettings,
248+
connectionPoolFactory,
249+
serverMonitorFactory,
250+
_eventAggregator);
251+
}
252+
253+
private IServerMonitorFactory CreateServerMonitorFactory()
254+
{
255+
var serverMonitorConnectionSettings = _connectionSettings
256+
.With(authenticators: new IAuthenticator[] { });
257+
258+
var heartbeatConnectTimeout = _tcpStreamSettings.ConnectTimeout;
259+
if (heartbeatConnectTimeout == TimeSpan.Zero || heartbeatConnectTimeout == Timeout.InfiniteTimeSpan)
260+
{
261+
heartbeatConnectTimeout = TimeSpan.FromSeconds(30);
262+
}
263+
var heartbeatSocketTimeout = _serverSettings.HeartbeatTimeout;
264+
if (heartbeatSocketTimeout == TimeSpan.Zero || heartbeatSocketTimeout == Timeout.InfiniteTimeSpan)
265+
{
266+
heartbeatSocketTimeout = heartbeatConnectTimeout;
267+
}
268+
var serverMonitorTcpStreamSettings = new TcpStreamSettings(_tcpStreamSettings)
269+
.With(
270+
connectTimeout: heartbeatConnectTimeout,
271+
readTimeout: heartbeatSocketTimeout,
272+
writeTimeout: heartbeatSocketTimeout
273+
);
274+
275+
var serverMonitorStreamFactory = CreateTcpStreamFactory(serverMonitorTcpStreamSettings);
276+
277+
var serverMonitorConnectionFactory = new BinaryConnectionFactory(
278+
serverMonitorConnectionSettings,
279+
serverMonitorStreamFactory,
280+
new EventAggregator());
281+
282+
return new ServerMonitorFactory(
283+
_serverSettings,
284+
serverMonitorConnectionFactory,
285+
_eventAggregator);
286+
}
287+
288+
private IStreamFactory CreateTcpStreamFactory(TcpStreamSettings tcpStreamSettings)
289+
{
290+
var streamFactory = (IStreamFactory)new TcpStreamFactory(tcpStreamSettings);
291+
if (_sslStreamSettings != null)
292+
{
293+
streamFactory = new SslStreamFactory(_sslStreamSettings, streamFactory);
294+
}
295+
296+
return _streamFactoryWrapper(streamFactory);
297+
}
245298
}
246299
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using System.Net.Sockets;
18+
using System.Threading;
1819
using MongoDB.Driver.Core.Misc;
1920

2021
namespace MongoDB.Driver.Core.Configuration
@@ -34,7 +35,7 @@ public class ServerSettings
3435
/// <summary>
3536
/// Gets the default heartbeat timeout.
3637
/// </summary>
37-
public static TimeSpan DefaultHeartbeatTimeout => TimeSpan.FromSeconds(10);
38+
public static TimeSpan DefaultHeartbeatTimeout => Timeout.InfiniteTimeSpan;
3839
#endregion
3940

4041
// fields

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ public TcpStreamSettings(
6363
_writeTimeout = Ensure.IsNullOrInfiniteOrGreaterThanOrEqualToZero(writeTimeout.WithDefault(null), "writeTimeout");
6464
}
6565

66+
internal TcpStreamSettings(TcpStreamSettings other)
67+
{
68+
_addressFamily = other.AddressFamily;
69+
_connectTimeout = other.ConnectTimeout;
70+
_readTimeout = other.ReadTimeout;
71+
_receiveBufferSize = other.ReceiveBufferSize;
72+
_sendBufferSize = other.SendBufferSize;
73+
_socketConfigurator = other.SocketConfigurator;
74+
_writeTimeout = other.WriteTimeout;
75+
}
76+
6677
// properties
6778
/// <summary>
6879
/// Gets the address family.

src/MongoDB.Driver/MongoClientSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public TimeSpan HeartbeatTimeout
241241
set
242242
{
243243
if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
244-
_heartbeatTimeout = Ensure.IsGreaterThanZero(value, nameof(value));
244+
_heartbeatTimeout = Ensure.IsInfiniteOrGreaterThanZero(value, nameof(value));
245245
}
246246
}
247247

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* Copyright 2018-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;
17+
using FluentAssertions;
18+
using MongoDB.Bson.TestHelpers;
19+
using MongoDB.Driver.Core.Authentication;
20+
using MongoDB.Driver.Core.Connections;
21+
using MongoDB.Driver.Core.Servers;
22+
using Xunit;
23+
24+
namespace MongoDB.Driver.Core.Configuration
25+
{
26+
public class ClusterBuilderTests
27+
{
28+
[Theory]
29+
[InlineData(0, 0, 30000, 30000)]
30+
[InlineData(-1, -1, 30000, 30000)]
31+
[InlineData(20000, 0, 20000, 20000)]
32+
[InlineData(20000, -1, 20000, 20000)]
33+
[InlineData(20000, 10000, 20000, 10000)]
34+
public void CreateServerMonitorFactory_should_return_expected_result(int connectTimeoutMilliseconds, int heartbeatTimeoutMilliseconds, int expectedServerMonitorConnectTimeoutMilliseconds, int expectedServerMonitorSocketTimeoutMilliseconds)
35+
{
36+
var connectTimeout = TimeSpan.FromMilliseconds(connectTimeoutMilliseconds);
37+
var authenticators = new[] { new DefaultAuthenticator(new UsernamePasswordCredential("source", "username", "password")) };
38+
var heartbeatTimeout = TimeSpan.FromMilliseconds(heartbeatTimeoutMilliseconds);
39+
var expectedServerMonitorConnectTimeout = TimeSpan.FromMilliseconds(expectedServerMonitorConnectTimeoutMilliseconds);
40+
var expectedServerMonitorSocketTimeout = TimeSpan.FromMilliseconds(expectedServerMonitorSocketTimeoutMilliseconds);
41+
var subject = new ClusterBuilder()
42+
.ConfigureTcp(s => s.With(connectTimeout: connectTimeout))
43+
.ConfigureConnection(s => s.With(authenticators: authenticators))
44+
.ConfigureServer(s => s.With(heartbeatTimeout: heartbeatTimeout));
45+
46+
var result = (ServerMonitorFactory)subject.CreateServerMonitorFactory();
47+
48+
var serverMonitorConnectionFactory = (BinaryConnectionFactory)result._connectionFactory();
49+
var serverMonitorConnectionSettings = serverMonitorConnectionFactory._settings();
50+
serverMonitorConnectionSettings.Authenticators.Should().HaveCount(0);
51+
52+
var serverMonitorStreamFactory = (TcpStreamFactory)serverMonitorConnectionFactory._streamFactory();
53+
var serverMonitorTcpStreamSettings = serverMonitorStreamFactory._settings();
54+
serverMonitorTcpStreamSettings.ConnectTimeout.Should().Be(expectedServerMonitorConnectTimeout);
55+
serverMonitorTcpStreamSettings.ReadTimeout.Should().Be(expectedServerMonitorSocketTimeout);
56+
serverMonitorTcpStreamSettings.WriteTimeout.Should().Be(expectedServerMonitorSocketTimeout);
57+
58+
var eventSuscriber = result._eventSubscriber();
59+
60+
var serverSettings = result._settings();
61+
}
62+
}
63+
64+
public static class ClusterBuilderReflector
65+
{
66+
internal static IServerMonitorFactory CreateServerMonitorFactory(this ClusterBuilder obj) => (IServerMonitorFactory)Reflector.Invoke(obj, nameof(CreateServerMonitorFactory));
67+
}
68+
}

tests/MongoDB.Driver.Core.Tests/Core/Configuration/ServerSettingsTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Threading;
1718
using FluentAssertions;
1819
using Xunit;
1920

@@ -34,7 +35,7 @@ public void DefaultHeartbeatTimeout_should_return_expected_result()
3435
{
3536
var result = ServerSettings.DefaultHeartbeatTimeout;
3637

37-
result.Should().Be(TimeSpan.FromSeconds(10));
38+
result.Should().Be(Timeout.InfiniteTimeSpan);
3839
}
3940

4041
[Fact]
@@ -49,15 +50,15 @@ public void constructor_should_initialize_instance()
4950
[Fact]
5051
public void constructor_should_throw_when_heartbeatInterval_is_negative()
5152
{
52-
Action action = () => new ServerSettings(heartbeatInterval: TimeSpan.FromSeconds(-1));
53+
Action action = () => new ServerSettings(heartbeatInterval: TimeSpan.FromSeconds(-2));
5354

5455
action.ShouldThrow<ArgumentException>().And.ParamName.Should().Be("heartbeatInterval");
5556
}
5657

5758
[Fact]
5859
public void constructor_should_throw_when_heartbeatTimeout_is_negative()
5960
{
60-
Action action = () => new ServerSettings(heartbeatTimeout: TimeSpan.FromSeconds(-1));
61+
Action action = () => new ServerSettings(heartbeatTimeout: TimeSpan.FromSeconds(-2));
6162

6263
action.ShouldThrow<ArgumentException>().And.ParamName.Should().Be("heartbeatTimeout");
6364
}

tests/MongoDB.Driver.Core.Tests/Core/Configuration/TcpStreamSettingsTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,30 @@ public void constructor_should_initialize_instance()
4040
subject.WriteTimeout.Should().Be(null);
4141
}
4242

43+
[Fact]
44+
public void constructor_with_other_should_initialize_instance()
45+
{
46+
var other = new TcpStreamSettings(
47+
addressFamily: AddressFamily.InterNetworkV6,
48+
connectTimeout: TimeSpan.FromSeconds(123),
49+
readTimeout: TimeSpan.FromSeconds(456),
50+
receiveBufferSize: 123,
51+
sendBufferSize: 456,
52+
socketConfigurator: (Action<Socket>)((Socket _) => { }),
53+
writeTimeout: TimeSpan.FromSeconds(789)
54+
);
55+
56+
var result = new TcpStreamSettings(other);
57+
58+
result.AddressFamily.Should().Be(other.AddressFamily);
59+
result.ConnectTimeout.Should().Be(other.ConnectTimeout);
60+
result.ReadTimeout.Should().Be(other.ReadTimeout);
61+
result.ReceiveBufferSize.Should().Be(other.ReceiveBufferSize);
62+
result.SendBufferSize.Should().Be(other.SendBufferSize);
63+
result.SocketConfigurator.Should().Be(other.SocketConfigurator);
64+
result.WriteTimeout.Should().Be(other.WriteTimeout);
65+
}
66+
4367
[Fact]
4468
public void constructor_should_throw_when_connectTimeout_is_negative()
4569
{

tests/MongoDB.Driver.Core.Tests/Core/Connections/BinaryConnectionFactoryTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Text;
2121
using System.Threading.Tasks;
2222
using FluentAssertions;
23+
using MongoDB.Bson.TestHelpers;
2324
using MongoDB.Driver.Core.Clusters;
2425
using MongoDB.Driver.Core.Configuration;
2526
using MongoDB.Driver.Core.Connections;
@@ -106,4 +107,10 @@ public void CreateConnection_should_return_a_BinaryConnection()
106107
connection.Should().BeOfType<BinaryConnection>();
107108
}
108109
}
110+
111+
public static class BinaryConnectionFactoryReflector
112+
{
113+
internal static ConnectionSettings _settings(this BinaryConnectionFactory obj) => (ConnectionSettings)Reflector.GetFieldValue(obj, nameof(_settings));
114+
internal static IStreamFactory _streamFactory(this BinaryConnectionFactory obj) => (IStreamFactory)Reflector.GetFieldValue(obj, nameof(_streamFactory));
115+
}
109116
}

tests/MongoDB.Driver.Core.Tests/Core/Connections/TcpStreamFactoryTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using MongoDB.Driver.Core.TestHelpers;
3030
using MongoDB.Bson.TestHelpers.XunitExtensions;
3131
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
32+
using MongoDB.Bson.TestHelpers;
3233

3334
namespace MongoDB.Driver.Core.Connections
3435
{
@@ -185,4 +186,9 @@ public void SocketConfigurator_can_be_used_to_set_keepAlive(
185186
keepAlive.Should().NotBe(0); // .NET returns 1 but Mono returns 8
186187
}
187188
}
189+
190+
public static class TcpStreamFactoryReflector
191+
{
192+
internal static TcpStreamSettings _settings(this TcpStreamFactory obj) => (TcpStreamSettings)Reflector.GetFieldValue(obj, nameof(_settings));
193+
}
188194
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* Copyright 2018-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 MongoDB.Bson.TestHelpers;
17+
using MongoDB.Driver.Core.Configuration;
18+
using MongoDB.Driver.Core.Connections;
19+
using MongoDB.Driver.Core.Events;
20+
21+
namespace MongoDB.Driver.Core.Servers
22+
{
23+
public static class ServerMonitorFactoryReflector
24+
{
25+
internal static IConnectionFactory _connectionFactory(this ServerMonitorFactory obj) => (IConnectionFactory)Reflector.GetFieldValue(obj, nameof(_connectionFactory));
26+
internal static IEventSubscriber _eventSubscriber(this ServerMonitorFactory obj) => (IEventSubscriber)Reflector.GetFieldValue(obj, nameof(_eventSubscriber));
27+
internal static ServerSettings _settings(this ServerMonitorFactory obj) => (ServerSettings)Reflector.GetFieldValue(obj, nameof(_settings));
28+
}
29+
}

0 commit comments

Comments
 (0)