From c240e1cabfe482b6cac05c46553befaba91152b7 Mon Sep 17 00:00:00 2001 From: Oleksandr Poliakov Date: Mon, 25 Nov 2024 17:18:26 -0800 Subject: [PATCH 1/4] CSHARP-3840: Unresponsive/deadlocked cluster.Dispose() --- .../Core/Connections/TcpStreamFactory.cs | 129 +++++++----------- 1 file changed, 51 insertions(+), 78 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs index 13325c3324e..54359716037 100644 --- a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs +++ b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs @@ -122,103 +122,76 @@ private void ConfigureConnectedSocket(Socket socket) private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancellationToken) { - var state = 1; // 1 == connecting, 2 == connected, 3 == timedout, 4 == cancelled + IAsyncResult connectOperation; - using (new Timer(_ => ChangeState(3), null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan)) - using (cancellationToken.Register(() => ChangeState(4))) + if (endPoint is DnsEndPoint dnsEndPoint) { - try - { - var dnsEndPoint = endPoint as DnsEndPoint; - if (dnsEndPoint != null) - { - // mono doesn't support DnsEndPoint in its BeginConnect method. - socket.Connect(dnsEndPoint.Host, dnsEndPoint.Port); - } - else - { - socket.Connect(endPoint); - } - ChangeState(2); // note: might not actually go to state 2 if already in state 3 or 4 - } - catch when (state == 1) - { - try { socket.Dispose(); } catch { } - throw; - } - catch when (state >= 3) - { - // a timeout or operation cancelled exception will be thrown instead - } + // mono doesn't support DnsEndPoint in its BeginConnect method. + connectOperation = socket.BeginConnect(dnsEndPoint.Host, dnsEndPoint.Port, null, null); + } + else + { + connectOperation = socket.BeginConnect(endPoint, null, null); + } - if (state == 3) - { - var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout); - throw new TimeoutException(message); - } - if (state == 4) { throw new OperationCanceledException(); } + WaitHandle.WaitAny(new[] { connectOperation.AsyncWaitHandle, cancellationToken.WaitHandle }, _settings.ConnectTimeout); + + if (!connectOperation.IsCompleted) + { + socket.Dispose(); + + cancellationToken.ThrowIfCancellationRequested(); + throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}."); } - void ChangeState(int to) + try { - var from = Interlocked.CompareExchange(ref state, to, 1); - if (from == 1 && to >= 3) - { - try { socket.Dispose(); } catch { } // disposing the socket aborts the connection attempt - } + socket.EndConnect(connectOperation); + } + catch + { + socket.Dispose(); + throw; } } private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationToken cancellationToken) { - var state = 1; // 1 == connecting, 2 == connected, 3 == timedout, 4 == cancelled + var timeoutTask = Task.Delay(_settings.ConnectTimeout, cancellationToken); + Task connectTask; - using (new Timer(_ => ChangeState(3), null, _settings.ConnectTimeout, Timeout.InfiniteTimeSpan)) - using (cancellationToken.Register(() => ChangeState(4))) - { - try - { - var dnsEndPoint = endPoint as DnsEndPoint; #if !NET472 - await socket.ConnectAsync(endPoint).ConfigureAwait(false); + connectTask = socket.ConnectAsync(endPoint); #else - if (dnsEndPoint != null) - { - // mono doesn't support DnsEndPoint in its BeginConnect method. - await Task.Factory.FromAsync(socket.BeginConnect(dnsEndPoint.Host, dnsEndPoint.Port, null, null), socket.EndConnect).ConfigureAwait(false); - } - else - { - await Task.Factory.FromAsync(socket.BeginConnect(endPoint, null, null), socket.EndConnect).ConfigureAwait(false); - } + if (endPoint is DnsEndPoint dnsEndPoint) + { + // mono doesn't support DnsEndPoint in its BeginConnect method. + connectTask = Task.Factory.FromAsync(socket.BeginConnect(dnsEndPoint.Host, dnsEndPoint.Port, null, null), socket.EndConnect); + } + else + { + connectTask = Task.Factory.FromAsync(socket.BeginConnect(endPoint, null, null), socket.EndConnect); + } #endif - ChangeState(2); // note: might not actually go to state 2 if already in state 3 or 4 - } - catch when (state == 1) - { - try { socket.Dispose(); } catch { } - throw; - } - catch when (state >= 3) - { - // a timeout or operation cancelled exception will be thrown instead - } - if (state == 3) - { - var message = string.Format("Timed out connecting to {0}. Timeout was {1}.", endPoint, _settings.ConnectTimeout); - throw new TimeoutException(message); - } - if (state == 4) { throw new OperationCanceledException(); } + await Task.WhenAny(connectTask, timeoutTask).ConfigureAwait(false); + + if (!connectTask.IsCompleted) + { + socket.Dispose(); + + cancellationToken.ThrowIfCancellationRequested(); + throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}."); } - void ChangeState(int to) + try { - var from = Interlocked.CompareExchange(ref state, to, 1); - if (from == 1 && to >= 3) - { - try { socket.Dispose(); } catch { } // disposing the socket aborts the connection attempt - } + await connectTask.ConfigureAwait(false); + } + catch + { + socket.Dispose(); + throw; } } From 39a37ee40a4a4bfc12d3a7d9a92cedeb4cef13bd Mon Sep 17 00:00:00 2001 From: Oleksandr Poliakov Date: Tue, 17 Dec 2024 14:27:49 -0800 Subject: [PATCH 2/4] PR --- src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs index 54359716037..bf4c842abdd 100644 --- a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs +++ b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs @@ -134,7 +134,7 @@ private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancell connectOperation = socket.BeginConnect(endPoint, null, null); } - WaitHandle.WaitAny(new[] { connectOperation.AsyncWaitHandle, cancellationToken.WaitHandle }, _settings.ConnectTimeout); + WaitHandle.WaitAny([connectOperation.AsyncWaitHandle, cancellationToken.WaitHandle], _settings.ConnectTimeout); if (!connectOperation.IsCompleted) { @@ -165,12 +165,11 @@ private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationTo #else if (endPoint is DnsEndPoint dnsEndPoint) { - // mono doesn't support DnsEndPoint in its BeginConnect method. - connectTask = Task.Factory.FromAsync(socket.BeginConnect(dnsEndPoint.Host, dnsEndPoint.Port, null, null), socket.EndConnect); + connectTask = socket.ConnectAsync(dnsEndPoint.Host, dnsEndPoint.Port); } else { - connectTask = Task.Factory.FromAsync(socket.BeginConnect(endPoint, null, null), socket.EndConnect); + connectTask = socket.ConnectAsync(endPoint); } #endif From 1958b00a2faa270fd2dac34177039d1fb30d8152 Mon Sep 17 00:00:00 2001 From: Oleksandr Poliakov Date: Wed, 18 Dec 2024 09:55:42 -0800 Subject: [PATCH 3/4] PR --- .../Core/Connections/TcpStreamFactory.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs index bf4c842abdd..de21de1fcc8 100644 --- a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs +++ b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs @@ -158,20 +158,7 @@ private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancell private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationToken cancellationToken) { var timeoutTask = Task.Delay(_settings.ConnectTimeout, cancellationToken); - Task connectTask; - -#if !NET472 - connectTask = socket.ConnectAsync(endPoint); -#else - if (endPoint is DnsEndPoint dnsEndPoint) - { - connectTask = socket.ConnectAsync(dnsEndPoint.Host, dnsEndPoint.Port); - } - else - { - connectTask = socket.ConnectAsync(endPoint); - } -#endif + var connectTask = socket.ConnectAsync(endPoint); await Task.WhenAny(connectTask, timeoutTask).ConfigureAwait(false); From f39077989a8beefd44f0fdaafa2d1c742a45762c Mon Sep 17 00:00:00 2001 From: Oleksandr Poliakov Date: Fri, 20 Dec 2024 09:19:55 -0800 Subject: [PATCH 4/4] PR --- src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs index de21de1fcc8..ae2c1fb69b4 100644 --- a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs +++ b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs @@ -138,7 +138,7 @@ private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancell if (!connectOperation.IsCompleted) { - socket.Dispose(); + try { socket.Dispose(); } catch { } cancellationToken.ThrowIfCancellationRequested(); throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}."); @@ -150,7 +150,7 @@ private void Connect(Socket socket, EndPoint endPoint, CancellationToken cancell } catch { - socket.Dispose(); + try { socket.Dispose(); } catch { } throw; } } @@ -164,7 +164,7 @@ private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationTo if (!connectTask.IsCompleted) { - socket.Dispose(); + try { socket.Dispose(); } catch { } cancellationToken.ThrowIfCancellationRequested(); throw new TimeoutException($"Timed out connecting to {endPoint}. Timeout was {_settings.ConnectTimeout}."); @@ -176,7 +176,7 @@ private async Task ConnectAsync(Socket socket, EndPoint endPoint, CancellationTo } catch { - socket.Dispose(); + try { socket.Dispose(); } catch { } throw; } }