Skip to content

Commit 17adfa1

Browse files
committed
CSHARP-2875: Fix race condition in TestStreamFactory CreateSttream method.
1 parent dfe9435 commit 17adfa1

File tree

1 file changed

+49
-39
lines changed

1 file changed

+49
-39
lines changed

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

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,10 @@ private void ConfigureConnectedSocket(Socket socket)
124124

125125
private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancellationToken)
126126
{
127-
var connected = false;
128-
var cancelled = false;
129-
var timedOut = false;
127+
var state = 1; // 1 == connecting, 2 == connected, 3 == timedout, 4 == cancelled
130128

131-
using (var registration = cancellationToken.Register(() => { if (!connected) { cancelled = true; try { socket.Dispose(); } catch { } } }))
132-
using (var timer = new Timer(_ => { if (!connected) { timedOut = true; try { socket.Dispose(); } catch { } } }, null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan))
129+
using (new Timer(_ => ChangeState(3), null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan))
130+
using (cancellationToken.Register(() => ChangeState(4)))
133131
{
134132
try
135133
{
@@ -143,43 +141,48 @@ private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancell
143141
{
144142
socket.Connect(endPoint);
145143
}
146-
connected = true;
147-
return;
144+
ChangeState(2); // note: might not actually go to state 2 if already in state 3 or 4
148145
}
149-
catch
146+
catch when (state == 1)
150147
{
151-
if (!cancelled && !timedOut)
152-
{
153-
try { socket.Dispose(); } catch { }
154-
throw;
155-
}
148+
try { socket.Dispose(); } catch { }
149+
throw;
150+
}
151+
catch when (state >= 3)
152+
{
153+
// a timeout or operation cancelled exception will be thrown instead
156154
}
157-
}
158155

159-
try { socket.Dispose(); } catch { }
156+
if (state == 3)
157+
{
158+
var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout);
159+
throw new TimeoutException(message);
160+
}
161+
if (state == 4) { throw new OperationCanceledException(); }
162+
}
160163

161-
cancellationToken.ThrowIfCancellationRequested();
162-
if (timedOut)
164+
void ChangeState(int to)
163165
{
164-
var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout);
165-
throw new TimeoutException(message);
166+
var from = Interlocked.CompareExchange(ref state, to, 1);
167+
if (from == 1 && to >= 3)
168+
{
169+
try { socket.Dispose(); } catch { } // disposing the socket aborts the connection attempt
170+
}
166171
}
167172
}
168173

169174
private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationToken cancellationToken)
170175
{
171-
var connected = false;
172-
var cancelled = false;
173-
var timedOut = false;
176+
var state = 1; // 1 == connecting, 2 == connected, 3 == timedout, 4 == cancelled
174177

175-
using (var registration = cancellationToken.Register(() => { if (!connected) { cancelled = true; try { socket.Dispose(); } catch { } } }))
176-
using (var timer = new Timer(_ => { if (!connected) { timedOut = true; try { socket.Dispose(); } catch { } } }, null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan))
178+
using (new Timer(_ => ChangeState(3), null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan))
179+
using (cancellationToken.Register(() => ChangeState(4)))
177180
{
178181
try
179182
{
180183
var dnsEndPoint = endPoint as DnsEndPoint;
181184
#if NETSTANDARD1_5 || NETSTANDARD1_6
182-
await socket.ConnectAsync(endPoint).ConfigureAwait(false); // TODO: honor cancellationToken
185+
await socket.ConnectAsync(endPoint).ConfigureAwait(false);
183186
#else
184187
if (dnsEndPoint != null)
185188
{
@@ -191,26 +194,33 @@ private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationTo
191194
await Task.Factory.FromAsync(socket.BeginConnect(endPoint, null, null), socket.EndConnect).ConfigureAwait(false);
192195
}
193196
#endif
194-
connected = true;
195-
return;
197+
ChangeState(2); // note: might not actually go to state 2 if already in state 3 or 4
196198
}
197-
catch
199+
catch when (state == 1)
198200
{
199-
if (!cancelled && !timedOut)
200-
{
201-
try { socket.Dispose(); } catch { }
202-
throw;
203-
}
201+
try { socket.Dispose(); } catch { }
202+
throw;
203+
}
204+
catch when (state >= 3)
205+
{
206+
// a timeout or operation cancelled exception will be thrown instead
204207
}
205-
}
206208

207-
try { socket.Dispose(); } catch { }
209+
if (state == 3)
210+
{
211+
var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout);
212+
throw new TimeoutException(message);
213+
}
214+
if (state == 4) { throw new OperationCanceledException(); }
215+
}
208216

209-
cancellationToken.ThrowIfCancellationRequested();
210-
if (timedOut)
217+
void ChangeState(int to)
211218
{
212-
var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout);
213-
throw new TimeoutException(message);
219+
var from = Interlocked.CompareExchange(ref state, to, 1);
220+
if (from == 1 && to >= 3)
221+
{
222+
try { socket.Dispose(); } catch { } // disposing the socket aborts the connection attempt
223+
}
214224
}
215225
}
216226

0 commit comments

Comments
 (0)