From 6d0993cfa20dd2b6a065f0c8de24c4b7b8f0ad41 Mon Sep 17 00:00:00 2001 From: David Engel Date: Thu, 3 Jul 2025 16:04:13 -0700 Subject: [PATCH 1/3] - Align SqlException Numbers across platforms - Better capture error scenarios in TCP managed SNI. - Fix logging bug in SqlClientEventSource. - Change nativeError from uint to int --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 10 +-- .../Data/SqlClient/TdsParser.netcore.cs | 2 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 8 +-- .../src/Interop/Windows/Sni/SniError.cs | 2 +- .../SqlClient/ManagedSni/SniCommon.netcore.cs | 4 +- .../SqlClient/ManagedSni/SniError.netcore.cs | 24 +++++-- .../ManagedSni/SniNpHandle.netcore.cs | 4 +- .../ManagedSni/SniTcpHandle.netcore.cs | 64 +++++++++++++++++-- .../Data/SqlClient/SqlClientEventSource.cs | 2 +- .../src/Microsoft/Data/SqlClient/SqlError.cs | 8 +-- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 2 +- 11 files changed, 99 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index b5febf7add..e0cd2a09b9 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1507,7 +1507,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) string providerRid = string.Format("SNI_PN{0}", details.Provider); string providerName = StringsHelper.GetResourceString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - uint win32ErrorCode = details.NativeError; + int win32ErrorCode = details.nativeError; SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); if (details.SniErrorNumber == 0) @@ -1557,7 +1557,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) { - errorMessage += LocalDbApi.GetLocalDbMessage((int)details.NativeError); + errorMessage += LocalDbApi.GetLocalDbMessage(details.nativeError); win32ErrorCode = 0; } SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); @@ -1567,10 +1567,10 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", - (int)details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); + details.nativeError, (int)details.lineNumber, details.function, details.exception, _server); - return new SqlError(infoNumber: (int)details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, - errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); + return new SqlError(infoNumber: details.nativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, + errorMessage, details.function, (int)details.lineNumber, win32ErrorCode: details.nativeError, details.exception); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs index 71ef5e9381..25a93fe682 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs @@ -13,7 +13,7 @@ internal sealed partial class TdsParser internal struct SNIErrorDetails { public string errorMessage; - public uint nativeError; + public int nativeError; public uint sniErrorNumber; public int provider; public uint lineNumber; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 3acd517f73..d89192de7a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1588,7 +1588,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) string providerRid = string.Format("SNI_PN{0}", (int)details.Provider); string providerName = StringsHelper.GetString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - uint win32ErrorCode = details.NativeError; + int win32ErrorCode = details.nativeError; if (details.SniErrorNumber == 0) { @@ -1626,15 +1626,15 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) { - errorMessage += LocalDbApi.GetLocalDbMessage((int)details.NativeError); + errorMessage += LocalDbApi.GetLocalDbMessage(details.nativeError); win32ErrorCode = 0; } } errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); - return new SqlError((int)details.NativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, - _server, errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode); + return new SqlError(details.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, + _server, errorMessage, details.function, (int)details.lineNumber, win32ErrorCode); } internal void CheckResetConnection(TdsParserStateObject stateObj) diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs index b349c2876d..2e519c15ab 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs @@ -12,7 +12,7 @@ internal struct SniError internal Provider provider; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261)] internal string errorMessage; - internal uint nativeError; + internal int nativeError; internal uint sniError; [MarshalAs(UnmanagedType.LPWStr)] internal string fileName; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs index 6aaf23f877..9653c94d34 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs @@ -189,7 +189,7 @@ internal static IPAddress[] GetDnsIpAddresses(string serverName) /// SNI error code /// Error message /// - internal static uint ReportSNIError(SniProviders provider, uint nativeError, uint sniError, string errorMessage) + internal static uint ReportSNIError(SniProviders provider, int nativeError, uint sniError, string errorMessage) { SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, native Error = {1}, SNI Error = {2}, Error Message = {3}", args0: provider, args1: nativeError, args2: sniError, args3: errorMessage); return ReportSNIError(new SniError(provider, nativeError, sniError, errorMessage)); @@ -203,7 +203,7 @@ internal static uint ReportSNIError(SniProviders provider, uint nativeError, uin /// SNI Exception /// Native SNI error code /// - internal static uint ReportSNIError(SniProviders provider, uint sniError, Exception sniException, uint nativeErrorCode = 0) + internal static uint ReportSNIError(SniProviders provider, uint sniError, Exception sniException, int nativeErrorCode = 0) { SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, SNI Error = {1}, Exception = {2}", args0: provider, args1: sniError, args2: sniException?.Message); return ReportSNIError(new SniError(provider, sniError, sniException, nativeErrorCode)); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs index 1a33de1d96..9cecb1e70f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs @@ -5,6 +5,8 @@ #if NET using System; +using System.ComponentModel; +using System.Net.Sockets; namespace Microsoft.Data.SqlClient.ManagedSni { @@ -14,17 +16,18 @@ namespace Microsoft.Data.SqlClient.ManagedSni internal class SniError { // Error numbers from native SNI implementation - internal const uint CertificateValidationErrorCode = 2148074277; + // This is signed int representation of the error code 0x80090325 + internal const int CertificateValidationErrorCode = -2146893019; public readonly SniProviders provider; public readonly string errorMessage; - public readonly uint nativeError; + public readonly int nativeError; public readonly uint sniError; public readonly string function; public readonly uint lineNumber; public readonly Exception exception; - public SniError(SniProviders provider, uint nativeError, uint sniErrorCode, string errorMessage) + public SniError(SniProviders provider, int nativeError, uint sniErrorCode, string errorMessage) { lineNumber = 0; function = string.Empty; @@ -35,12 +38,25 @@ public SniError(SniProviders provider, uint nativeError, uint sniErrorCode, stri exception = null; } - public SniError(SniProviders provider, uint sniErrorCode, Exception sniException, uint nativeErrorCode = 0) + public SniError(SniProviders provider, uint sniErrorCode, Exception sniException, int nativeErrorCode = 0) { lineNumber = 0; function = string.Empty; this.provider = provider; nativeError = nativeErrorCode; + if (nativeErrorCode == 0) + { + if (sniException is SocketException socketException) + { + // SocketErrorCode values are cross-plat consistent in .NET (matching native Windows error codes) + // underlying type of SocketErrorCode is int + nativeError = (int)socketException.SocketErrorCode; + } + else if (sniException is Win32Exception win32Exception) + { + nativeError = win32Exception.NativeErrorCode; // Replicates native SNI behavior + } + } sniError = sniErrorCode; errorMessage = string.Empty; exception = sniException; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs index e244209f23..7a1c2ec660 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs @@ -203,7 +203,7 @@ public override uint Receive(out SniPacket packet, int timeout) packet = null; var e = new Win32Exception(); SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, Packet length found 0, Win32 exception raised: {1}", args0: _connectionId, args1: e?.Message); - return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message); + return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message); } } catch (ObjectDisposedException ode) @@ -413,7 +413,7 @@ private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniExceptio return SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.InternalExceptionError, sniException); } - private uint ReportErrorAndReleasePacket(SniPacket packet, uint nativeError, uint sniError, string errorMessage) + private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage) { if (packet != null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs index 656060beeb..4370e7c483 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs @@ -375,6 +375,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, IEnumerable ipAddresses = GetHostAddressesSortedByPreference(serverName, ipPreference); + SocketException lastSocketException = null; + foreach (IPAddress ipAddress in ipAddresses) { bool isSocketSelected = false; @@ -426,7 +428,7 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, { if (timeout.IsExpired) { - return null; + throw new Win32Exception(258, "The operation has timed out."); } int socketSelectTimeout = @@ -442,10 +444,24 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout); // nothing selected means timeout + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, + "Socket.Select results: checkReadLst.Count: {0}, checkWriteLst.Count: {1}, checkErrorLst.Count: {2}", + checkReadLst.Count, checkWriteLst.Count, checkErrorLst.Count); } while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0); // workaround: false positive socket.Connected on linux: https://github.com/dotnet/runtime/issues/55538 isConnected = socket.Connected && checkErrorLst.Count == 0; + if (!isConnected) + { + // Retrieve the socket error code + int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error); + SocketError socketError = (SocketError)socketErrorCode; + + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, + "Socket connection failed. SocketError: {0} ({1})", socketError, socketErrorCode); + + lastSocketException = new SocketException(socketErrorCode); + } } if (isConnected) @@ -463,6 +479,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, } pendingDNSInfo = new SQLDNSInfo(cachedFQDN, iPv4String, iPv6String, port.ToString()); isSocketSelected = true; + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, + "Connected to socket: {0}", socket.RemoteEndPoint); return socket; } } @@ -471,6 +489,7 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, SqlClientEventSource.Log.TryAdvancedTraceEvent( "{0}.{1}{2}THIS EXCEPTION IS BEING SWALLOWED: {3}", nameof(SniTcpHandle), nameof(Connect), EventType.ERR, e); + lastSocketException = e; } finally { @@ -479,6 +498,14 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, } } + if (lastSocketException != null) + { + SqlClientEventSource.Log.TryAdvancedTraceEvent( + "{0}.{1}{2}Last Socket Exception: {3}", + nameof(SniTcpHandle), nameof(Connect), EventType.ERR, lastSocketException); + throw lastSocketException; + } + return null; } } @@ -574,6 +601,20 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout); // nothing selected means select timed out } while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0 && !timeout.IsExpired); + foreach (Socket socket in checkErrorLst) + { + // Retrieve the socket error code + int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error); + SocketError socketError = (SocketError)socketErrorCode; + + // Log any failed sockets + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, + "Socket connection failed for {0}. SocketError: {1} ({2})", + sockets[socket], socketError, socketErrorCode); + + lastError = new SocketException(socketErrorCode); + } + } catch (SocketException e) { @@ -588,6 +629,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim { SqlClientEventSource.Log.TryAdvancedTraceEvent( "{0}.{1}{2}ParallelConnect timeout expired.", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.INFO); + // We will throw below after cleanup break; } @@ -654,9 +696,19 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim if (connectedSocket == null) { + if (timeout.IsExpired) + { + throw new Win32Exception(258, "The operation has timed out."); + } + SqlClientEventSource.Log.TryAdvancedTraceEvent( - "{0}.{1}{2}No socket connections succeeded. Last error: {3}", + "{0}.{1}{2} No socket connections succeeded. Last error: {3}", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.ERR, lastError); + + if (lastError != null) + { + throw lastError; + } } return connectedSocket; @@ -861,7 +913,7 @@ public override uint Receive(out SniPacket packet, int timeoutInMilliseconds) packet = null; var e = new Win32Exception(); SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Win32 exception occurred: {1}", args0: _connectionId, args1: e?.Message); - return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message); + return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message); } SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Data read from stream synchronously", args0: _connectionId); @@ -992,13 +1044,13 @@ public override uint CheckConnection() return TdsEnums.SNI_SUCCESS; } - private uint ReportTcpSNIError(Exception sniException, uint nativeErrorCode = 0) + private uint ReportTcpSNIError(Exception sniException, int nativeErrorCode = 0) { _status = TdsEnums.SNI_ERROR; return SniCommon.ReportSNIError(SniProviders.TCP_PROV, SniCommon.InternalExceptionError, sniException, nativeErrorCode); } - private uint ReportTcpSNIError(uint nativeError, uint sniError, string errorMessage) + private uint ReportTcpSNIError(int nativeError, uint sniError, string errorMessage) { _status = TdsEnums.SNI_ERROR; return SniCommon.ReportSNIError(SniProviders.TCP_PROV, nativeError, sniError, errorMessage); @@ -1013,7 +1065,7 @@ private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniExceptio return ReportTcpSNIError(sniException); } - private uint ReportErrorAndReleasePacket(SniPacket packet, uint nativeError, uint sniError, string errorMessage) + private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage) { if (packet != null) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs index 90a69b5670..cf21d2b0c5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs @@ -424,7 +424,7 @@ internal long TryScopeEnterEvent(string className, [System.Runtime.CompilerServi { StringBuilder sb = new StringBuilder(className); sb.Append(".").Append(memberName).Append(" | INFO | SCOPE | Entering Scope {0}"); - return SNIScopeEnter(sb.ToString()); + return ScopeEnter(sb.ToString()); } return 0; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs index 6390729bad..fc3a1247f3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs @@ -33,16 +33,16 @@ public sealed class SqlError // to find and invoke the functions, changing the signatures will break many // things elsewhere - internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, uint win32ErrorCode, Exception exception = null) + internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, int win32ErrorCode, Exception exception = null) : this(infoNumber, errorState, errorClass, server, errorMessage, procedure, lineNumber, win32ErrorCode, exception, -1) { } - internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, uint win32ErrorCode, Exception exception, int batchIndex) + internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, int win32ErrorCode, Exception exception, int batchIndex) : this(infoNumber, errorState, errorClass, server, errorMessage, procedure, lineNumber, exception, batchIndex) { _server = server; - _win32ErrorCode = (int)win32ErrorCode; + _win32ErrorCode = win32ErrorCode; } internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, Exception exception = null) @@ -103,7 +103,7 @@ public override string ToString() /// public int LineNumber => _lineNumber; - internal int Win32ErrorCode => _win32ErrorCode; + internal int Win32ErrorCode => _win32ErrorCode; internal Exception Exception => _exception; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index cf8ae33606..3113e19625 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -607,7 +607,7 @@ public enum ActiveDirectoryWorkflow : byte public const uint SNI_UNINITIALIZED = unchecked((uint)-1); public const uint SNI_SUCCESS = 0; // The operation completed successfully. public const uint SNI_ERROR = 1; // Error - public const uint SNI_WAIT_TIMEOUT = 258; // The wait operation timed out. + public const int SNI_WAIT_TIMEOUT = 258; // The wait operation timed out. public const uint SNI_SUCCESS_IO_PENDING = 997; // Overlapped I/O operation is in progress. // Windows Sockets Error Codes From d33760ed5228efa722850525be361baf9a789e99 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:22:14 -0300 Subject: [PATCH 2/3] - Removed duplicate SniErrorDetails object and aligned field names. --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 10 +++++----- .../src/Microsoft/Data/SqlClient/TdsParser.netcore.cs | 11 ----------- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 8 ++++---- .../Microsoft/Data/SqlClient/TdsParserStateObject.cs | 4 ++-- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index e0cd2a09b9..7a226aafaa 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1507,7 +1507,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) string providerRid = string.Format("SNI_PN{0}", details.Provider); string providerName = StringsHelper.GetResourceString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - int win32ErrorCode = details.nativeError; + int win32ErrorCode = details.NativeError; SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); if (details.SniErrorNumber == 0) @@ -1557,7 +1557,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) { - errorMessage += LocalDbApi.GetLocalDbMessage(details.nativeError); + errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); win32ErrorCode = 0; } SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); @@ -1567,10 +1567,10 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", - details.nativeError, (int)details.lineNumber, details.function, details.exception, _server); + details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); - return new SqlError(infoNumber: details.nativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, - errorMessage, details.function, (int)details.lineNumber, win32ErrorCode: details.nativeError, details.exception); + return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, + errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs index 25a93fe682..95dd0d9731 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs @@ -10,17 +10,6 @@ namespace Microsoft.Data.SqlClient { internal sealed partial class TdsParser { - internal struct SNIErrorDetails - { - public string errorMessage; - public int nativeError; - public uint sniErrorNumber; - public int provider; - public uint lineNumber; - public string function; - public Exception exception; - } - internal static void FillGuidBytes(Guid guid, Span buffer) => guid.TryWriteBytes(buffer); internal static void FillDoubleBytes(double value, Span buffer) => BinaryPrimitives.TryWriteInt64LittleEndian(buffer, BitConverter.DoubleToInt64Bits(value)); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index d89192de7a..25fa8631f8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1588,7 +1588,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) string providerRid = string.Format("SNI_PN{0}", (int)details.Provider); string providerName = StringsHelper.GetString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - int win32ErrorCode = details.nativeError; + int win32ErrorCode = details.NativeError; if (details.SniErrorNumber == 0) { @@ -1626,15 +1626,15 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) { - errorMessage += LocalDbApi.GetLocalDbMessage(details.nativeError); + errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); win32ErrorCode = 0; } } errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); - return new SqlError(details.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, - _server, errorMessage, details.function, (int)details.lineNumber, win32ErrorCode); + return new SqlError(details.NativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, + _server, errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode); } internal void CheckResetConnection(TdsParserStateObject stateObj) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 781f360991..f2e7ff75d2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -55,14 +55,14 @@ internal enum SnapshottedStateFlags : byte internal readonly struct SniErrorDetails { public readonly string ErrorMessage; - public readonly uint NativeError; + public readonly int NativeError; public readonly uint SniErrorNumber; public readonly int Provider; public readonly uint LineNumber; public readonly string Function; public readonly Exception Exception; - internal SniErrorDetails(string errorMessage, uint nativeError, uint sniErrorNumber, int provider, uint lineNumber, string function, Exception exception = null) + internal SniErrorDetails(string errorMessage, int nativeError, uint sniErrorNumber, int provider, uint lineNumber, string function, Exception exception = null) { ErrorMessage = errorMessage; NativeError = nativeError; From 5b6cd7e1afb956e52497c6139c8dfcaf45db11d4 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Sat, 12 Jul 2025 08:18:36 -0300 Subject: [PATCH 3/3] - Added localized string for the new connection timed out exception. --- .../Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs | 8 ++++++-- .../src/Resources/Strings.Designer.cs | 9 +++++++++ src/Microsoft.Data.SqlClient/src/Resources/Strings.resx | 5 ++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs index 4370e7c483..8960801967 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs @@ -428,7 +428,9 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, { if (timeout.IsExpired) { - throw new Win32Exception(258, "The operation has timed out."); + throw new Win32Exception( + TdsEnums.SNI_WAIT_TIMEOUT, + StringsHelper.GetString(Strings.SQL_ConnectTimeout)); } int socketSelectTimeout = @@ -698,7 +700,9 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim { if (timeout.IsExpired) { - throw new Win32Exception(258, "The operation has timed out."); + throw new Win32Exception( + TdsEnums.SNI_WAIT_TIMEOUT, + StringsHelper.GetString(Strings.SQL_ConnectTimeout)); } SqlClientEventSource.Log.TryAdvancedTraceEvent( diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index b17b0a1750..dc8684f1f3 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -9597,6 +9597,15 @@ internal static string SQL_ExRoutingDestination { } } + /// + /// Looks up a localized string similar to: The connection attempt timed out. + /// + internal static string SQL_ConnectTimeout { + get { + return ResourceManager.GetString("SQL_ConnectTimeout", resourceCulture); + } + } + /// /// Looks up a localized string similar to Timeout expired. The connection has been broken as a result.. /// diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index 1e903b8a79..8d59dd7ae4 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -2520,6 +2520,9 @@ This command requires an asynchronous connection. Set "Asynchronous Processing=true" in the connection string. + + The connection attempt timed out. + Timeout expired. The connection has been broken as a result. @@ -4677,4 +4680,4 @@ {0} Invalid JSON string for vector. - \ No newline at end of file +