Skip to content

Commit 2349b02

Browse files
authored
CSHARP-3840: Unresponsive/deadlocked cluster.Dispose() (#1557)
1 parent 476af2b commit 2349b02

File tree

1 file changed

+39
-80
lines changed

1 file changed

+39
-80
lines changed

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

Lines changed: 39 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -122,103 +122,62 @@ private void ConfigureConnectedSocket(Socket socket)
122122

123123
private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancellationToken)
124124
{
125-
var state = 1; // 1 == connecting, 2 == connected, 3 == timedout, 4 == cancelled
125+
IAsyncResult connectOperation;
126126

127-
using (new Timer(_ => ChangeState(3), null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan))
128-
using (cancellationToken.Register(() => ChangeState(4)))
127+
if (endPoint is DnsEndPoint dnsEndPoint)
129128
{
130-
try
131-
{
132-
var dnsEndPoint = endPoint as DnsEndPoint;
133-
if (dnsEndPoint != null)
134-
{
135-
// mono doesn't support DnsEndPoint in its BeginConnect method.
136-
socket.Connect(dnsEndPoint.Host, dnsEndPoint.Port);
137-
}
138-
else
139-
{
140-
socket.Connect(endPoint);
141-
}
142-
ChangeState(2); // note: might not actually go to state 2 if already in state 3 or 4
143-
}
144-
catch when (state == 1)
145-
{
146-
try { socket.Dispose(); } catch { }
147-
throw;
148-
}
149-
catch when (state >= 3)
150-
{
151-
// a timeout or operation cancelled exception will be thrown instead
152-
}
129+
// mono doesn't support DnsEndPoint in its BeginConnect method.
130+
connectOperation = socket.BeginConnect(dnsEndPoint.Host, dnsEndPoint.Port, null, null);
131+
}
132+
else
133+
{
134+
connectOperation = socket.BeginConnect(endPoint, null, null);
135+
}
153136

154-
if (state == 3)
155-
{
156-
var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout);
157-
throw new TimeoutException(message);
158-
}
159-
if (state == 4) { throw new OperationCanceledException(); }
137+
WaitHandle.WaitAny([connectOperation.AsyncWaitHandle, cancellationToken.WaitHandle], _settings.ConnectTimeout);
138+
139+
if (!connectOperation.IsCompleted)
140+
{
141+
try { socket.Dispose(); } catch { }
142+
143+
cancellationToken.ThrowIfCancellationRequested();
144+
throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}.");
160145
}
161146

162-
void ChangeState(int to)
147+
try
163148
{
164-
var from = Interlocked.CompareExchange(ref state, to, 1);
165-
if (from == 1 && to >= 3)
166-
{
167-
try { socket.Dispose(); } catch { } // disposing the socket aborts the connection attempt
168-
}
149+
socket.EndConnect(connectOperation);
150+
}
151+
catch
152+
{
153+
try { socket.Dispose(); } catch { }
154+
throw;
169155
}
170156
}
171157

172158
private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationToken cancellationToken)
173159
{
174-
var state = 1; // 1 == connecting, 2 == connected, 3 == timedout, 4 == cancelled
160+
var timeoutTask = Task.Delay(_settings.ConnectTimeout, cancellationToken);
161+
var connectTask = socket.ConnectAsync(endPoint);
162+
163+
await Task.WhenAny(connectTask, timeoutTask).ConfigureAwait(false);
175164

176-
using (new Timer(_ => ChangeState(3), null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan))
177-
using (cancellationToken.Register(() => ChangeState(4)))
165+
if (!connectTask.IsCompleted)
178166
{
179-
try
180-
{
181-
var dnsEndPoint = endPoint as DnsEndPoint;
182-
#if !NET472
183-
await socket.ConnectAsync(endPoint).ConfigureAwait(false);
184-
#else
185-
if (dnsEndPoint != null)
186-
{
187-
// mono doesn't support DnsEndPoint in its BeginConnect method.
188-
await Task.Factory.FromAsync(socket.BeginConnect(dnsEndPoint.Host, dnsEndPoint.Port, null, null), socket.EndConnect).ConfigureAwait(false);
189-
}
190-
else
191-
{
192-
await Task.Factory.FromAsync(socket.BeginConnect(endPoint, null, null), socket.EndConnect).ConfigureAwait(false);
193-
}
194-
#endif
195-
ChangeState(2); // note: might not actually go to state 2 if already in state 3 or 4
196-
}
197-
catch when (state == 1)
198-
{
199-
try { socket.Dispose(); } catch { }
200-
throw;
201-
}
202-
catch when (state >= 3)
203-
{
204-
// a timeout or operation cancelled exception will be thrown instead
205-
}
167+
try { socket.Dispose(); } catch { }
206168

207-
if (state == 3)
208-
{
209-
var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout);
210-
throw new TimeoutException(message);
211-
}
212-
if (state == 4) { throw new OperationCanceledException(); }
169+
cancellationToken.ThrowIfCancellationRequested();
170+
throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}.");
213171
}
214172

215-
void ChangeState(int to)
173+
try
216174
{
217-
var from = Interlocked.CompareExchange(ref state, to, 1);
218-
if (from == 1 && to >= 3)
219-
{
220-
try { socket.Dispose(); } catch { } // disposing the socket aborts the connection attempt
221-
}
175+
await connectTask.ConfigureAwait(false);
176+
}
177+
catch
178+
{
179+
try { socket.Dispose(); } catch { }
180+
throw;
222181
}
223182
}
224183

0 commit comments

Comments
 (0)