Skip to content

Commit 9a88686

Browse files
CSHARP-2861: Having incorrect credentials along with minPoolSize > 0 makes the driver to endlessly create new connections.
1 parent eb6dc40 commit 9a88686

File tree

3 files changed

+154
-16
lines changed

3 files changed

+154
-16
lines changed

src/MongoDB.Driver.Core/Core/ConnectionPools/ExclusiveConnectionPool.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,12 @@ private async Task MaintainSizeAsync()
258258
{
259259
await PrunePoolAsync(maintenanceCancellationToken).ConfigureAwait(false);
260260
await EnsureMinSizeAsync(maintenanceCancellationToken).ConfigureAwait(false);
261-
await Task.Delay(_settings.MaintenanceInterval, maintenanceCancellationToken).ConfigureAwait(false);
262261
}
263262
catch
264263
{
265-
// do nothing, this is called in the background and, quite frankly, should never
266-
// result in an error
264+
// ignore exceptions
267265
}
266+
await Task.Delay(_settings.MaintenanceInterval, maintenanceCancellationToken).ConfigureAwait(false);
268267
}
269268
}
270269

@@ -322,7 +321,15 @@ private async Task EnsureMinSizeAsync(CancellationToken cancellationToken)
322321
// when adding in a connection, we need to open it because
323322
// the whole point of having a min pool size is to have
324323
// them available and ready...
325-
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
324+
try
325+
{
326+
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
327+
}
328+
catch (Exception)
329+
{
330+
connection.Dispose();
331+
throw;
332+
}
326333
_connectionHolder.Return(connection);
327334
stopwatch.Stop();
328335

tests/MongoDB.Driver.Core.Tests/Core/ConnectionPools/ExclusiveConnectionPoolTests.cs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
using System.Collections.Generic;
1818
using System.Net;
1919
using System.Threading;
20+
using System.Threading.Tasks;
2021
using FluentAssertions;
22+
using MongoDB.Bson.TestHelpers;
2123
using MongoDB.Bson.TestHelpers.XunitExtensions;
2224
using MongoDB.Driver.Core.Clusters;
2325
using MongoDB.Driver.Core.Configuration;
@@ -62,12 +64,7 @@ public ExclusiveConnectionPoolTests()
6264
waitQueueSize: 1,
6365
waitQueueTimeout: TimeSpan.FromSeconds(2));
6466

65-
_subject = new ExclusiveConnectionPool(
66-
_serverId,
67-
_endPoint,
68-
_settings,
69-
_mockConnectionFactory.Object,
70-
_capturedEvents);
67+
_subject = CreateSubject();
7168
}
7269

7370
[Fact]
@@ -463,6 +460,66 @@ public void Initialize_should_scale_up_the_number_of_connections_to_min_size()
463460
}
464461
}
465462

463+
[Fact]
464+
public void MaintainSizeAsync_should_call_connection_dispose_when_connection_authentication_fail()
465+
{
466+
var authenticationFailedConnection = new Mock<IConnection>();
467+
authenticationFailedConnection
468+
.Setup(c => c.OpenAsync(It.IsAny<CancellationToken>())) // an authentication exception is thrown from _connectionInitializer.InitializeConnection
469+
// that in turn is called from OpenAsync
470+
.Throws(new MongoAuthenticationException(new ConnectionId(_serverId), "test message"));
471+
472+
using (var subject = CreateSubject())
473+
{
474+
_mockConnectionFactory
475+
.Setup(f => f.CreateConnection(_serverId, _endPoint))
476+
.Returns(() =>
477+
{
478+
subject._maintenanceCancellationTokenSource().Cancel(); // Task.Delay will be canceled
479+
return authenticationFailedConnection.Object;
480+
});
481+
482+
var _ = Record.Exception(() => subject.MaintainSizeAsync().GetAwaiter().GetResult());
483+
authenticationFailedConnection.Verify(conn => conn.Dispose(), Times.Once);
484+
}
485+
}
486+
487+
[Fact]
488+
public void MaintainSizeAsync_should_not_try_new_attempt_after_failing_without_delay()
489+
{
490+
var settings =_settings.With(maintenanceInterval: TimeSpan.FromSeconds(10));
491+
492+
using (var subject = CreateSubject(settings))
493+
{
494+
_mockConnectionFactory
495+
.SetupSequence(f => f.CreateConnection(_serverId, _endPoint))
496+
.Throws<Exception>() // failed attempt
497+
.Returns(() => // successful attempt which should be delayed
498+
{
499+
// break the loop. With this line the MaintainSizeAsync will contain only 2 iterations
500+
subject._maintenanceCancellationTokenSource().Cancel();
501+
return new MockConnection(_serverId);
502+
});
503+
504+
var testResult = Task.WaitAny(
505+
subject.MaintainSizeAsync(), // if this task is completed first, it will mean that there was no delay (10 sec)
506+
Task.Delay(TimeSpan.FromSeconds(1))); // time to be sure that delay is happening,
507+
// if the method is running more than 1 second, then delay is happening
508+
testResult.Should().Be(1);
509+
}
510+
}
511+
512+
// private methods
513+
private ExclusiveConnectionPool CreateSubject(ConnectionPoolSettings connectionPoolSettings = null)
514+
{
515+
return new ExclusiveConnectionPool(
516+
_serverId,
517+
_endPoint,
518+
connectionPoolSettings ?? _settings,
519+
_mockConnectionFactory.Object,
520+
_capturedEvents);
521+
}
522+
466523
private void InitializeAndWait()
467524
{
468525
_subject.Initialize();
@@ -481,4 +538,17 @@ private void InitializeAndWait()
481538
_subject.UsedCount.Should().Be(0);
482539
}
483540
}
541+
542+
internal static class ExclusiveConnectionPoolReflector
543+
{
544+
public static CancellationTokenSource _maintenanceCancellationTokenSource(this ExclusiveConnectionPool obj)
545+
{
546+
return (CancellationTokenSource)Reflector.GetFieldValue(obj, nameof(_maintenanceCancellationTokenSource));
547+
}
548+
549+
public static Task MaintainSizeAsync(this ExclusiveConnectionPool obj)
550+
{
551+
return (Task)Reflector.Invoke(obj, nameof(MaintainSizeAsync));
552+
}
553+
}
484554
}

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

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,11 @@
1818
using System.Net.Sockets;
1919
using System.Threading;
2020
using FluentAssertions;
21-
using MongoDB.Driver.Core.Clusters;
2221
using MongoDB.Driver.Core.Configuration;
23-
using MongoDB.Driver.Core.Connections;
24-
using MongoDB.Driver.Core.Servers;
2522
using Xunit;
2623
using System.Threading.Tasks;
2724
using System.Reflection;
2825
using System.IO;
29-
using MongoDB.Driver.Core.TestHelpers;
3026
using MongoDB.Bson.TestHelpers.XunitExtensions;
3127
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
3228
using MongoDB.Bson.TestHelpers;
@@ -35,6 +31,37 @@ namespace MongoDB.Driver.Core.Connections
3531
{
3632
public class TcpStreamFactoryTests
3733
{
34+
[Theory]
35+
[ParameterAttributeData]
36+
public void Connect_should_dispose_socket_if_socket_fails([Values(false, true)] bool async)
37+
{
38+
RequireServer.Check();
39+
40+
var subject = new TcpStreamFactory();
41+
var endpoint = new DnsEndPoint("test", 80); // not existed endpoint which will fail when we call socket.Connect
42+
43+
using (var testSocket = new TestSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
44+
{
45+
Exception exception;
46+
if (async)
47+
{
48+
exception = Record.Exception(
49+
() =>
50+
subject
51+
.ConnectAsync(testSocket, endpoint, CancellationToken.None)
52+
.GetAwaiter()
53+
.GetResult());
54+
}
55+
else
56+
{
57+
exception = Record.Exception(() => subject.Connect(testSocket, endpoint, CancellationToken.None));
58+
}
59+
60+
exception.Should().NotBeNull();
61+
testSocket.DisposeAttempts.Should().Be(1);
62+
}
63+
}
64+
3865
[Fact]
3966
public void Constructor_should_throw_an_ArgumentNullException_when_tcpStreamSettings_is_null()
4067
{
@@ -125,7 +152,7 @@ public void CreateStream_should_call_the_socketConfigurator(
125152

126153
if (async)
127154
{
128-
subject.CreateStreamAsync(endPoint, CancellationToken.None).GetAwaiter().GetResult();
155+
subject.CreateStreamAsync(endPoint, CancellationToken.None).GetAwaiter().GetResult();
129156
}
130157
else
131158
{
@@ -185,10 +212,44 @@ public void SocketConfigurator_can_be_used_to_set_keepAlive(
185212
var keepAlive = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive);
186213
keepAlive.Should().NotBe(0); // .NET returns 1 but Mono returns 8
187214
}
215+
216+
// nested types
217+
private class TestSocket : Socket
218+
{
219+
public int DisposeAttempts { get; set; } = 0;
220+
221+
public TestSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) : base(addressFamily, socketType, protocolType)
222+
{
223+
}
224+
225+
public TestSocket(SocketType socketType, ProtocolType protocolType) : base(socketType, protocolType)
226+
{
227+
}
228+
229+
public TestSocket(SocketInformation socketInformation) : base(socketInformation)
230+
{
231+
}
232+
233+
protected override void Dispose(bool disposing)
234+
{
235+
base.Dispose(disposing);
236+
DisposeAttempts++;
237+
}
238+
}
188239
}
189240

190-
public static class TcpStreamFactoryReflector
241+
internal static class TcpStreamFactoryReflector
191242
{
192243
internal static TcpStreamSettings _settings(this TcpStreamFactory obj) => (TcpStreamSettings)Reflector.GetFieldValue(obj, nameof(_settings));
244+
245+
internal static void Connect(this TcpStreamFactory obj, Socket socket, EndPoint endPoint, CancellationToken cancellationToken)
246+
{
247+
Reflector.Invoke(obj, nameof(Connect), socket, endPoint, cancellationToken);
248+
}
249+
250+
internal static Task ConnectAsync(this TcpStreamFactory obj, Socket socket, EndPoint endPoint, CancellationToken cancellationToken)
251+
{
252+
return (Task)Reflector.Invoke(obj, nameof(ConnectAsync), socket, endPoint, cancellationToken);
253+
}
193254
}
194255
}

0 commit comments

Comments
 (0)