Skip to content

Commit 0620e0c

Browse files
committed
pr
1 parent 930bead commit 0620e0c

File tree

9 files changed

+141
-147
lines changed

9 files changed

+141
-147
lines changed

src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -165,41 +165,57 @@ private void ConfigureConnectedSocket(Socket socket)
165165

166166
private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancellationToken)
167167
{
168-
IAsyncResult connectOperation;
169-
#if NET472
170-
if (endPoint is DnsEndPoint dnsEndPoint)
171-
{
172-
// mono doesn't support DnsEndPoint in its BeginConnect method.
173-
connectOperation = socket.BeginConnect(dnsEndPoint.Host, dnsEndPoint.Port, null, null);
174-
}
175-
else
176-
{
177-
connectOperation = socket.BeginConnect(endPoint, null, null);
178-
}
179-
#else
180-
connectOperation = socket.BeginConnect(endPoint, null, null);
181-
#endif
182-
183-
WaitHandle.WaitAny([connectOperation.AsyncWaitHandle, cancellationToken.WaitHandle], _settings.ConnectTimeout);
184-
185-
if (!connectOperation.IsCompleted)
168+
var wasCallbackExecuted = false;
169+
using var timeoutCancellationTokenSource = new CancellationTokenSource(_settings.ConnectTimeout);
170+
using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token);
171+
using var cancellationSubscription = combinedCancellationTokenSource.Token.Register(() =>
186172
{
187173
try
188174
{
175+
wasCallbackExecuted = true;
189176
socket.Dispose();
190-
} catch { }
191-
192-
cancellationToken.ThrowIfCancellationRequested();
193-
throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}.");
194-
}
177+
}
178+
catch
179+
{
180+
// Ignore any exception here, as we should avoid throwing in callback.
181+
}
182+
});
195183

196184
try
197185
{
198-
socket.EndConnect(connectOperation);
186+
#if NET472
187+
if (endPoint is DnsEndPoint dnsEndPoint)
188+
{
189+
// mono doesn't support DnsEndPoint in its Connect method.
190+
socket.Connect(dnsEndPoint.Host, dnsEndPoint.Port);
191+
}
192+
else
193+
{
194+
socket.Connect(endPoint);
195+
}
196+
#else
197+
socket.Connect(endPoint);
198+
#endif
199199
}
200-
catch
200+
catch (Exception)
201201
{
202-
try { socket.Dispose(); } catch { }
202+
if (!wasCallbackExecuted)
203+
{
204+
try
205+
{
206+
socket.Dispose();
207+
}
208+
catch (Exception)
209+
{
210+
}
211+
}
212+
213+
cancellationToken.ThrowIfCancellationRequested();
214+
if (timeoutCancellationTokenSource.IsCancellationRequested)
215+
{
216+
throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}.");
217+
}
218+
203219
throw;
204220
}
205221
}

tests/MongoDB.Driver.Tests/ClusterRegistryTests.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
using MongoDB.Driver.Core.Misc;
2828
using MongoDB.Driver.Core.Servers;
2929
using MongoDB.Driver.Core.TestHelpers.Logging;
30-
using MongoDB.Driver.TestHelpers;
3130
using Xunit;
3231
using Xunit.Abstractions;
3332

@@ -73,7 +72,6 @@ public void DisposingClusterSource_should_use_cluster_registry_and_return_cluste
7372
ClusterRegistry.Instance._registry().Keys.Should().NotContain(clusterKey);
7473
}
7574

76-
#if WINDOWS
7775
[Fact]
7876
public void Instance_should_return_the_same_instance_every_time()
7977
{
@@ -221,7 +219,6 @@ public void UnregisterAndDisposeCluster_should_unregister_and_dispose_the_cluste
221219
subject._registry().Count.Should().Be(0);
222220
cluster._state().Should().Be(2);
223221
}
224-
#endif
225222
}
226223

227224
internal static class ClusterRegistryReflector

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

Lines changed: 29 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class TcpStreamFactoryTests
3434
{
3535
[Theory]
3636
[ParameterAttributeData]
37-
public void Connect_should_dispose_socket_if_socket_fails([Values(false, true)] bool async)
37+
public async Task Connect_should_dispose_socket_if_socket_fails([Values(false, true)] bool async)
3838
{
3939
RequireServer.Check();
4040

@@ -43,20 +43,9 @@ public void Connect_should_dispose_socket_if_socket_fails([Values(false, true)]
4343

4444
using (var testSocket = new TestSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
4545
{
46-
Exception exception;
47-
if (async)
48-
{
49-
exception = Record.Exception(
50-
() =>
51-
subject
52-
.ConnectAsync(testSocket, endpoint, CancellationToken.None)
53-
.GetAwaiter()
54-
.GetResult());
55-
}
56-
else
57-
{
58-
exception = Record.Exception(() => subject.Connect(testSocket, endpoint, CancellationToken.None));
59-
}
46+
var exception = async ?
47+
await Record.ExceptionAsync(() => subject.ConnectAsync(testSocket, endpoint, CancellationToken.None)) :
48+
Record.Exception(() => subject.Connect(testSocket, endpoint, CancellationToken.None));
6049

6150
exception.Should().NotBeNull();
6251
testSocket.DisposeAttempts.Should().Be(1);
@@ -66,83 +55,64 @@ public void Connect_should_dispose_socket_if_socket_fails([Values(false, true)]
6655
[Fact]
6756
public void Constructor_should_throw_an_ArgumentNullException_when_tcpStreamSettings_is_null()
6857
{
69-
Action act = () => new TcpStreamFactory(null);
58+
var exception = Record.Exception(() => new TcpStreamFactory(null));
7059

71-
act.ShouldThrow<ArgumentNullException>();
60+
exception.Should().BeOfType<ArgumentNullException>().Subject
61+
.ParamName.Should().Be("settings");
7262
}
7363

7464
[Theory]
7565
[ParameterAttributeData]
76-
public void CreateStream_should_throw_a_SocketException_when_the_endpoint_could_not_be_resolved(
77-
[Values(false, true)]
78-
bool async)
66+
public async Task CreateStream_should_throw_a_SocketException_when_the_endpoint_could_not_be_resolved([Values(false, true)]bool async)
7967
{
8068
var subject = new TcpStreamFactory();
8169

82-
Action act;
83-
if (async)
84-
{
85-
act = () => subject.CreateStreamAsync(new DnsEndPoint("not-gonna-exist-i-hope", 27017), CancellationToken.None).GetAwaiter().GetResult();
86-
}
87-
else
88-
{
89-
act = () => subject.CreateStream(new DnsEndPoint("not-gonna-exist-i-hope", 27017), CancellationToken.None);
90-
}
70+
var exception = async ?
71+
await Record.ExceptionAsync(() => subject.CreateStreamAsync(new DnsEndPoint("not-gonna-exist-i-hope", 27017), CancellationToken.None)) :
72+
Record.Exception(() => subject.CreateStream(new DnsEndPoint("not-gonna-exist-i-hope", 27017), CancellationToken.None));
9173

92-
act.ShouldThrow<SocketException>();
74+
exception.Should().BeAssignableTo<SocketException>();
9375
}
9476

9577
[Theory]
9678
[ParameterAttributeData]
97-
public void CreateStream_should_throw_when_cancellation_is_requested(
98-
[Values(false, true)]
99-
bool async)
79+
public async Task CreateStream_should_throw_when_cancellation_is_requested([Values(false, true)]bool async)
10080
{
10181
var subject = new TcpStreamFactory();
10282
var endPoint = new IPEndPoint(new IPAddress(0x01010101), 12345); // a non-existent host and port
10383
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(20));
10484

105-
Action action;
85+
var exception = async ?
86+
await Record.ExceptionAsync(() => subject.CreateStreamAsync(endPoint, cancellationTokenSource.Token)) :
87+
Record.Exception(() => subject.CreateStream(endPoint, cancellationTokenSource.Token));
10688
if (async)
10789
{
108-
action = () => subject.CreateStreamAsync(endPoint, cancellationTokenSource.Token).GetAwaiter().GetResult();
90+
exception.Should().BeOfType<TaskCanceledException>();
10991
}
11092
else
11193
{
112-
action = () => subject.CreateStream(endPoint, cancellationTokenSource.Token);
94+
exception.Should().BeOfType<OperationCanceledException>();
11395
}
114-
115-
action.ShouldThrow<OperationCanceledException>();
11696
}
11797

11898
[Theory]
11999
[ParameterAttributeData]
120-
public void CreateStream_should_throw_when_connect_timeout_has_expired(
121-
[Values(false, true)]
122-
bool async)
100+
public async Task CreateStream_should_throw_when_connect_timeout_has_expired([Values(false, true)]bool async)
123101
{
124102
var settings = new TcpStreamSettings(connectTimeout: TimeSpan.FromMilliseconds(20));
125103
var subject = new TcpStreamFactory(settings);
126104
var endPoint = new IPEndPoint(new IPAddress(0x01010101), 12345); // a non-existent host and port
127105

128-
Action action;
129-
if (async)
130-
{
131-
action = () => subject.CreateStreamAsync(endPoint, CancellationToken.None).GetAwaiter().GetResult(); ;
132-
}
133-
else
134-
{
135-
action = () => subject.CreateStream(endPoint, CancellationToken.None);
136-
}
106+
var exception = async ?
107+
await Record.ExceptionAsync(() => subject.CreateStreamAsync(endPoint, CancellationToken.None)) :
108+
Record.Exception(() => subject.CreateStream(endPoint, CancellationToken.None));
137109

138-
action.ShouldThrow<TimeoutException>();
110+
exception.Should().BeOfType<TimeoutException>();
139111
}
140112

141113
[Theory]
142114
[ParameterAttributeData]
143-
public void CreateStream_should_call_the_socketConfigurator(
144-
[Values(false, true)]
145-
bool async)
115+
public async Task CreateStream_should_call_the_socketConfigurator([Values(false, true)]bool async)
146116
{
147117
RequireServer.Check();
148118
var socketConfiguratorWasCalled = false;
@@ -153,7 +123,7 @@ public void CreateStream_should_call_the_socketConfigurator(
153123

154124
if (async)
155125
{
156-
subject.CreateStreamAsync(endPoint, CancellationToken.None).GetAwaiter().GetResult();
126+
await subject.CreateStreamAsync(endPoint, CancellationToken.None);
157127
}
158128
else
159129
{
@@ -165,9 +135,7 @@ public void CreateStream_should_call_the_socketConfigurator(
165135

166136
[Theory]
167137
[ParameterAttributeData]
168-
public void CreateStream_should_connect_to_a_running_server_and_return_a_non_null_stream(
169-
[Values(false, true)]
170-
bool async)
138+
public async Task CreateStream_should_connect_to_a_running_server_and_return_a_non_null_stream([Values(false, true)]bool async)
171139
{
172140
RequireServer.Check();
173141
var subject = new TcpStreamFactory();
@@ -176,7 +144,7 @@ public void CreateStream_should_connect_to_a_running_server_and_return_a_non_nul
176144
Stream stream;
177145
if (async)
178146
{
179-
stream = subject.CreateStreamAsync(endPoint, CancellationToken.None).GetAwaiter().GetResult();
147+
stream = await subject.CreateStreamAsync(endPoint, CancellationToken.None);
180148
}
181149
else
182150
{
@@ -188,9 +156,7 @@ public void CreateStream_should_connect_to_a_running_server_and_return_a_non_nul
188156

189157
[Theory]
190158
[ParameterAttributeData]
191-
public void SocketConfigurator_can_be_used_to_set_keepAlive(
192-
[Values(false, true)]
193-
bool async)
159+
public async Task SocketConfigurator_can_be_used_to_set_keepAlive([Values(false, true)]bool async)
194160
{
195161
RequireServer.Check();
196162
Action<Socket> socketConfigurator = s => s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
@@ -201,7 +167,7 @@ public void SocketConfigurator_can_be_used_to_set_keepAlive(
201167
Stream stream;
202168
if (async)
203169
{
204-
stream = subject.CreateStreamAsync(endPoint, CancellationToken.None).GetAwaiter().GetResult();
170+
stream = await subject.CreateStreamAsync(endPoint, CancellationToken.None);
205171
}
206172
else
207173
{

tests/MongoDB.Driver.Tests/UnobservedTaskExceptionTracking.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
1617
using FluentAssertions;
1718
using MongoDB.TestHelpers.XunitExtensions.TimeoutEnforcing;
1819

@@ -23,7 +24,10 @@ public class UnobservedTaskExceptionTracking
2324
[UnobservedExceptionTrackingFact]
2425
public void EnsureNoUnobservedTaskException()
2526
{
26-
UnobservedExceptionTrackingTestCase.__unobservedExceptions.Should().BeEmpty();
27+
GC.Collect();
28+
GC.WaitForPendingFinalizers();
29+
30+
UnobservedExceptionTestDiscoverer.UnobservedExceptions.Should().BeEmpty();
2731
}
2832
}
2933

tests/MongoDB.TestHelpers/MongoDB.TestHelpers.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@
1212
<Description>Helper classes applicable to all test projects.</Description>
1313
</PropertyGroup>
1414

15+
<PropertyGroup>
16+
<DefineConstants>$(DefineConstants);UNOBSERVED_TASK_EXCEPTION_DEBUGGING</DefineConstants>
17+
</PropertyGroup>
18+
1519
</Project>

tests/MongoDB.TestHelpers/XunitExtensions/TimeoutEnforcing/TimeoutEnforcingTestInvoker.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ private async Task<decimal> InvokeBaseOnTaskScheduler(object testClassInstance)
8080

8181
protected override async Task<decimal> InvokeTestMethodAsync(object testClassInstance)
8282
{
83+
#if UNOBSERVED_TASK_EXCEPTION_DEBUGGING
84+
Exception unobservedException = null;
85+
#endif
8386
var xUnitTestCase = Test.TestCase as IXunitTestCase;
8487
var timeoutMS = xUnitTestCase?.Timeout ?? 0;
8588
var timeout = Debugger.IsAttached
@@ -92,9 +95,24 @@ protected override async Task<decimal> InvokeTestMethodAsync(object testClassIns
9295
decimal result;
9396
try
9497
{
98+
#if UNOBSERVED_TASK_EXCEPTION_DEBUGGING
99+
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionEventHandler;
100+
#endif
101+
95102
var baseTask = InvokeBaseOnTaskScheduler(testClassInstance);
96103
var resultTask = await Task.WhenAny(baseTask, Task.Delay(timeout));
97104

105+
#if UNOBSERVED_TASK_EXCEPTION_DEBUGGING
106+
GC.Collect();
107+
GC.WaitForPendingFinalizers();
108+
TaskScheduler.UnobservedTaskException -= UnobservedTaskExceptionEventHandler;
109+
110+
if (unobservedException != null)
111+
{
112+
throw unobservedException;
113+
}
114+
#endif
115+
98116
if (resultTask != baseTask)
99117
{
100118
throw new TestTimeoutException((int)timeout.TotalMilliseconds);
@@ -120,6 +138,13 @@ protected override async Task<decimal> InvokeTestMethodAsync(object testClassIns
120138
}
121139

122140
return result;
141+
142+
#if UNOBSERVED_TASK_EXCEPTION_DEBUGGING
143+
void UnobservedTaskExceptionEventHandler(object sender, UnobservedTaskExceptionEventArgs unobservedExceptionArgs)
144+
{
145+
unobservedException = unobservedExceptionArgs.Exception;
146+
}
147+
#endif
123148
}
124149
}
125150
}

0 commit comments

Comments
 (0)