From f777bd8495891d6e905c6895c914688acf757a78 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:20:02 +0100 Subject: [PATCH 01/25] Move TdsParserStateObjectManaged.cs --- .../netcore/src/Microsoft.Data.SqlClient.csproj | 4 +++- .../Data/SqlClient/TdsParserStateObjectManaged.netcore.cs} | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename src/Microsoft.Data.SqlClient/{netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs => src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.netcore.cs} (100%) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 5c67ef9d5b..5d06d69703 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -783,6 +783,9 @@ Microsoft\Data\SqlClient\TdsParserStateObject.Multiplexer.cs + + Microsoft\Data\SqlClient\TdsParserStateObjectManaged.netcore.cs + Microsoft\Data\SqlClient\TdsParserStaticMethods.cs @@ -835,7 +838,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.netcore.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.netcore.cs From 06060c9e6cacd531cea06cbb70771522371a8698 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:26:55 +0100 Subject: [PATCH 02/25] Merge netfx-only variables --- .../Data/SqlClient/TdsParserStateObject.netfx.cs | 6 ------ .../src/Microsoft/Data/SqlClient/TdsParserStateObject.cs | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 194beaca5f..2d0a6d9c17 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,12 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - // Used for blanking out password in trace. - internal int _tracePasswordOffset = 0; - internal int _tracePasswordLength = 0; - internal int _traceChangePasswordOffset = 0; - internal int _traceChangePasswordLength = 0; - ////////////////// // Constructors // ////////////////// 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 967c70250a..1f2e8b741d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -290,6 +290,14 @@ private enum SnapshotStatus // as the error handling may end up calling Dispose. private int _readingCount; +#if NETFRAMEWORK + // Used for blanking out password in trace. + internal int _tracePasswordOffset = 0; + internal int _tracePasswordLength = 0; + internal int _traceChangePasswordOffset = 0; + internal int _traceChangePasswordLength = 0; +#endif + // Test hooks #if DEBUG // This is a test hook to enable testing of the retry paths. From 1c73f139fdf3c176b5168764a52f40075a92e7a6 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:29:07 +0100 Subject: [PATCH 03/25] Merge constructor --- .../SqlClient/TdsParserStateObject.netcore.cs | 36 ------------------- .../SqlClient/TdsParserStateObject.netfx.cs | 36 ------------------- .../Data/SqlClient/TdsParserStateObject.cs | 32 +++++++++++++++++ 3 files changed, 32 insertions(+), 72 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 9111a19eec..3065b29ee6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,42 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - ////////////////// - // Constructors // - ////////////////// - - protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) - { - // Construct a MARS session - Debug.Assert(parser != null, "no parser?"); - _parser = parser; - _onTimeoutAsync = OnTimeoutAsync; - SniContext = SniContext.Snix_GetMarsSession; - - Debug.Assert(_parser._physicalStateObj != null, "no physical session?"); - Debug.Assert(_parser._physicalStateObj._inBuff != null, "no in buffer?"); - Debug.Assert(_parser._physicalStateObj._outBuff != null, "no out buffer?"); - Debug.Assert(_parser._physicalStateObj._outBuff.Length == - _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers."); - - // Determine packet size based on physical connection buffer lengths. - SetPacketSize(_parser._physicalStateObj._outBuff.Length); - - CreateSessionHandle(physicalConnection, async); - - if (IsFailedHandle()) - { - AddError(parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(); - } - - // we post a callback that represents the call to dispose; once the - // object is disposed, the next callback will cause the GC Handle to - // be released. - IncrementPendingCallbacks(); - _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; - } - ///////////////////// // General methods // ///////////////////// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 2d0a6d9c17..7eafc291a1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,42 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - ////////////////// - // Constructors // - ////////////////// - - protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) - { - // Construct a MARS session - Debug.Assert(parser != null, "no parser?"); - _parser = parser; - _onTimeoutAsync = OnTimeoutAsync; - SniContext = SniContext.Snix_GetMarsSession; - - Debug.Assert(_parser._physicalStateObj != null, "no physical session?"); - Debug.Assert(_parser._physicalStateObj._inBuff != null, "no in buffer?"); - Debug.Assert(_parser._physicalStateObj._outBuff != null, "no out buffer?"); - Debug.Assert(_parser._physicalStateObj._outBuff.Length == - _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers."); - - // Determine packet size based on physical connection buffer lengths. - SetPacketSize(_parser._physicalStateObj._outBuff.Length); - - CreateSessionHandle(physicalConnection, async); - - if (IsFailedHandle()) - { - AddError(parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(); - } - - // we post a callback that represents the call to dispose; once the - // object is disposed, the next callback will cause the GC Handle to - // be released. - IncrementPendingCallbacks(); - _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; - } - ///////////////////// // General methods // ///////////////////// 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 1f2e8b741d..d3e435cd3e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -360,6 +360,38 @@ protected TdsParserStateObject(TdsParser parser) _lastSuccessfulIOTimer = new LastIOTimer(); } + protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) + { + // Construct a MARS session + Debug.Assert(parser != null, "no parser?"); + _parser = parser; + _onTimeoutAsync = OnTimeoutAsync; + SniContext = SniContext.Snix_GetMarsSession; + + Debug.Assert(_parser._physicalStateObj != null, "no physical session?"); + Debug.Assert(_parser._physicalStateObj._inBuff != null, "no in buffer?"); + Debug.Assert(_parser._physicalStateObj._outBuff != null, "no out buffer?"); + Debug.Assert(_parser._physicalStateObj._outBuff.Length == + _parser._physicalStateObj._inBuff.Length, "Unexpected unequal buffers."); + + // Determine packet size based on physical connection buffer lengths. + SetPacketSize(_parser._physicalStateObj._outBuff.Length); + + CreateSessionHandle(physicalConnection, async); + + if (IsFailedHandle()) + { + AddError(parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(); + } + + // we post a callback that represents the call to dispose; once the + // object is disposed, the next callback will cause the GC Handle to + // be released. + IncrementPendingCallbacks(); + _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; + } + private void SetSnapshottedState(SnapshottedStateFlags flag, bool value) { if (value) From b8bfc28dc88bf5e0c2b9d16637e4fcf221b43378 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:32:51 +0100 Subject: [PATCH 04/25] netfx: sync trace string --- .../src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 7eafc291a1..4ded61d499 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -23,7 +23,7 @@ internal partial class TdsParserStateObject internal int DecrementPendingCallbacks(bool release) { int remaining = Interlocked.Decrement(ref _pendingCallbacks); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after decrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks); + SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); FreeGcHandle(remaining, release); From 3779e3b7841cf56ced654d228940ab47e0412d66 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:34:19 +0100 Subject: [PATCH 05/25] Merge DecrementPendingCallbacks --- .../SqlClient/TdsParserStateObject.netcore.cs | 17 ----------------- .../SqlClient/TdsParserStateObject.netfx.cs | 17 ----------------- .../Data/SqlClient/TdsParserStateObject.cs | 13 +++++++++++++ 3 files changed, 13 insertions(+), 34 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 3065b29ee6..70d57919ce 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,23 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - ///////////////////// - // General methods // - ///////////////////// - - internal int DecrementPendingCallbacks(bool release) - { - int remaining = Interlocked.Decrement(ref _pendingCallbacks); - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); - - FreeGcHandle(remaining, release); - - // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed - // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it - Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}"); - return remaining; - } - /// /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) /// NOTE: This is not safe to do on a connection that is currently in use diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 4ded61d499..6e2b1ce528 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,23 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - ///////////////////// - // General methods // - ///////////////////// - - internal int DecrementPendingCallbacks(bool release) - { - int remaining = Interlocked.Decrement(ref _pendingCallbacks); - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); - - FreeGcHandle(remaining, release); - - // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed - // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it - Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}"); - return remaining; - } - /// /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) /// NOTE: This is not safe to do on a connection that is currently in use 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 d3e435cd3e..3d7a645d5d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -1014,6 +1014,19 @@ internal int IncrementPendingCallbacks() return remaining; } + internal int DecrementPendingCallbacks(bool release) + { + int remaining = Interlocked.Decrement(ref _pendingCallbacks); + SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); + + FreeGcHandle(remaining, release); + + // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed + // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it + Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}"); + return remaining; + } + internal bool Deactivate() { bool goodForReuse = false; From 1910cb8842890fb2fbc0d3be90c896c1ff8915b3 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:37:23 +0100 Subject: [PATCH 06/25] Merge ValidateSNIConnection --- .../SqlClient/TdsParserStateObject.netcore.cs | 32 ------------------- .../SqlClient/TdsParserStateObject.netfx.cs | 32 ------------------- .../Data/SqlClient/TdsParserStateObject.cs | 32 +++++++++++++++++++ 3 files changed, 32 insertions(+), 64 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 70d57919ce..55c6240131 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,38 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - /// - /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) - /// NOTE: This is not safe to do on a connection that is currently in use - /// NOTE: This will mark the connection as broken if it is found to be dead - /// - /// True if the connection is still alive, otherwise false - internal bool ValidateSNIConnection() - { - if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) - { - return false; - } - - if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow) - { - return true; - } - - uint error = TdsEnums.SNI_SUCCESS; - SniContext = SniContext.Snix_Connect; - try - { - Interlocked.Increment(ref _readingCount); - error = CheckConnection(); - } - finally - { - Interlocked.Decrement(ref _readingCount); - } - return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT); - } - // This method should only be called by ReadSni! If not - it may have problems with timeouts! private void ReadSniError(TdsParserStateObject stateObj, uint error) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 6e2b1ce528..8128a6d783 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,38 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - /// - /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) - /// NOTE: This is not safe to do on a connection that is currently in use - /// NOTE: This will mark the connection as broken if it is found to be dead - /// - /// True if the connection is still alive, otherwise false - internal bool ValidateSNIConnection() - { - if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) - { - return false; - } - - if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow) - { - return true; - } - - uint error = TdsEnums.SNI_SUCCESS; - SniContext = SniContext.Snix_Connect; - try - { - Interlocked.Increment(ref _readingCount); - error = CheckConnection(); - } - finally - { - Interlocked.Decrement(ref _readingCount); - } - return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT); - } - // This method should only be called by ReadSni! If not - it may have problems with timeouts! private void ReadSniError(TdsParserStateObject stateObj, uint error) { 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 3d7a645d5d..e514c6888c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -951,6 +951,38 @@ internal void CancelRequest() } } + /// + /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) + /// NOTE: This is not safe to do on a connection that is currently in use + /// NOTE: This will mark the connection as broken if it is found to be dead + /// + /// True if the connection is still alive, otherwise false + internal bool ValidateSNIConnection() + { + if ((_parser == null) || ((_parser.State == TdsParserState.Broken) || (_parser.State == TdsParserState.Closed))) + { + return false; + } + + if (DateTime.UtcNow.Ticks - _lastSuccessfulIOTimer._value <= CheckConnectionWindow) + { + return true; + } + + uint error = TdsEnums.SNI_SUCCESS; + SniContext = SniContext.Snix_Connect; + try + { + Interlocked.Increment(ref _readingCount); + error = CheckConnection(); + } + finally + { + Interlocked.Decrement(ref _readingCount); + } + return (error == TdsEnums.SNI_SUCCESS) || (error == TdsEnums.SNI_WAIT_TIMEOUT); + } + public void CheckSetResetConnectionState(uint error, CallbackType callbackType) { // Should only be called for MARS - that is the only time we need to take From 0228eb3674c98c12aab373dd20bca8901d97cbaa Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:40:31 +0100 Subject: [PATCH 07/25] netfx, netcore: sync TransparentNetworkIPResolution error handling --- .../Data/SqlClient/TdsParserStateObject.netcore.cs | 7 ++++++- .../Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 55c6240131..8ab4e320f7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -93,7 +93,12 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error) // For DbMirroring Failover during login, never break the connection, just close the TdsParser _parser.Disconnect(); } - else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) + else if ((_parser.State == TdsParserState.OpenNotLoggedIn) +#if NETFRAMEWORK + && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution)) +#else + && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) +#endif { // For MultiSubnet Failover during login, never break the connection, just close the TdsParser _parser.Disconnect(); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 8128a6d783..b681c1abec 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -93,7 +93,12 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error) // For DbMirroring Failover during login, never break the connection, just close the TdsParser _parser.Disconnect(); } - else if ((_parser.State == TdsParserState.OpenNotLoggedIn) && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution)) + else if ((_parser.State == TdsParserState.OpenNotLoggedIn) +#if NETFRAMEWORK + && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution)) +#else + && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) +#endif { // For MultiSubnet Failover during login, never break the connection, just close the TdsParser _parser.Disconnect(); From 8d82f4d296e0e0912b8969ebaa23e56df045eb8e Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:46:27 +0100 Subject: [PATCH 08/25] Merge ReadSniError --- .../SqlClient/TdsParserStateObject.netcore.cs | 109 ------------------ .../SqlClient/TdsParserStateObject.netfx.cs | 109 ------------------ .../Data/SqlClient/TdsParserStateObject.cs | 109 ++++++++++++++++++ 3 files changed, 109 insertions(+), 218 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 8ab4e320f7..47d836134a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,115 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - // This method should only be called by ReadSni! If not - it may have problems with timeouts! - private void ReadSniError(TdsParserStateObject stateObj, uint error) - { - if (TdsEnums.SNI_WAIT_TIMEOUT == error) - { - Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); - bool fail = false; - - if (IsTimeoutStateExpired) - { // This is now our second timeout - time to give up. - fail = true; - } - else - { - stateObj.SetTimeoutStateStopped(); - Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - - if (!stateObj._attentionSent) - { - if (stateObj.Parser.State == TdsParserState.OpenLoggedIn) - { - stateObj.SendAttention(mustTakeWriteLock: true); - - PacketHandle syncReadPacket = default; - bool readFromNetwork = true; - bool shouldDecrement = false; - try - { - Interlocked.Increment(ref _readingCount); - shouldDecrement = true; - readFromNetwork = !PartialPacketContainsCompletePacket(); - if (readFromNetwork) - { - syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error); - } - else - { - error = TdsEnums.SNI_SUCCESS; - } - - Interlocked.Decrement(ref _readingCount); - shouldDecrement = false; - - if (TdsEnums.SNI_SUCCESS == error) - { - // We will end up letting the run method deal with the expected done:done_attn token stream. - stateObj.ProcessSniPacket(syncReadPacket, TdsEnums.SNI_SUCCESS); - return; - } - else - { - Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease"); - fail = true; // Subsequent read failed, time to give up. - } - } - finally - { - if (shouldDecrement) - { - Interlocked.Decrement(ref _readingCount); - } - - if (readFromNetwork && !IsPacketEmpty(syncReadPacket)) - { - // Be sure to release packet, otherwise it will be leaked by native. - ReleasePacket(syncReadPacket); - } - } - } - else - { - if (_parser._loginWithFailover) - { - // For DbMirroring Failover during login, never break the connection, just close the TdsParser - _parser.Disconnect(); - } - else if ((_parser.State == TdsParserState.OpenNotLoggedIn) -#if NETFRAMEWORK - && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution)) -#else - && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) -#endif - { - // For MultiSubnet Failover during login, never break the connection, just close the TdsParser - _parser.Disconnect(); - } - else - fail = true; // We aren't yet logged in - just fail. - } - } - } - - if (fail) - { - _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit! - _parser.Connection.BreakConnection(); - } - } - else - { - // Caution: ProcessSNIError always returns a fatal error! - AddError(_parser.ProcessSNIError(stateObj)); - } - ThrowExceptionAndWarning(); - - AssertValidState(); - } - private uint GetSniPacket(PacketHandle packet, ref uint dataSize) { return SniPacketGetData(packet, _inBuff, ref dataSize); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index b681c1abec..c1e50fa308 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,115 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - // This method should only be called by ReadSni! If not - it may have problems with timeouts! - private void ReadSniError(TdsParserStateObject stateObj, uint error) - { - if (TdsEnums.SNI_WAIT_TIMEOUT == error) - { - Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); - bool fail = false; - - if (IsTimeoutStateExpired) - { // This is now our second timeout - time to give up. - fail = true; - } - else - { - stateObj.SetTimeoutStateStopped(); - Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - - if (!stateObj._attentionSent) - { - if (stateObj.Parser.State == TdsParserState.OpenLoggedIn) - { - stateObj.SendAttention(mustTakeWriteLock: true); - - PacketHandle syncReadPacket = default; - bool readFromNetwork = true; - bool shouldDecrement = false; - try - { - Interlocked.Increment(ref _readingCount); - shouldDecrement = true; - readFromNetwork = !PartialPacketContainsCompletePacket(); - if (readFromNetwork) - { - syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error); - } - else - { - error = TdsEnums.SNI_SUCCESS; - } - - Interlocked.Decrement(ref _readingCount); - shouldDecrement = false; - - if (TdsEnums.SNI_SUCCESS == error) - { - // We will end up letting the run method deal with the expected done:done_attn token stream. - stateObj.ProcessSniPacket(syncReadPacket, TdsEnums.SNI_SUCCESS); - return; - } - else - { - Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease"); - fail = true; // Subsequent read failed, time to give up. - } - } - finally - { - if (shouldDecrement) - { - Interlocked.Decrement(ref _readingCount); - } - - if (readFromNetwork && !IsPacketEmpty(syncReadPacket)) - { - // Be sure to release packet, otherwise it will be leaked by native. - ReleasePacket(syncReadPacket); - } - } - } - else - { - if (_parser._loginWithFailover) - { - // For DbMirroring Failover during login, never break the connection, just close the TdsParser - _parser.Disconnect(); - } - else if ((_parser.State == TdsParserState.OpenNotLoggedIn) -#if NETFRAMEWORK - && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution)) -#else - && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) -#endif - { - // For MultiSubnet Failover during login, never break the connection, just close the TdsParser - _parser.Disconnect(); - } - else - fail = true; // We aren't yet logged in - just fail. - } - } - } - - if (fail) - { - _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit! - _parser.Connection.BreakConnection(); - } - } - else - { - // Caution: ProcessSNIError always returns a fatal error! - AddError(_parser.ProcessSNIError(stateObj)); - } - ThrowExceptionAndWarning(); - - AssertValidState(); - } - private uint GetSniPacket(PacketHandle packet, ref uint dataSize) { return SniPacketGetData(packet, _inBuff, ref dataSize); 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 e514c6888c..828202f556 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3440,6 +3440,115 @@ internal void ReadSni(TaskCompletionSource completion) } } + // This method should only be called by ReadSni! If not - it may have problems with timeouts! + private void ReadSniError(TdsParserStateObject stateObj, uint error) + { + if (TdsEnums.SNI_WAIT_TIMEOUT == error) + { + Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); + bool fail = false; + + if (IsTimeoutStateExpired) + { // This is now our second timeout - time to give up. + fail = true; + } + else + { + stateObj.SetTimeoutStateStopped(); + Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); + AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); + + if (!stateObj._attentionSent) + { + if (stateObj.Parser.State == TdsParserState.OpenLoggedIn) + { + stateObj.SendAttention(mustTakeWriteLock: true); + + PacketHandle syncReadPacket = default; + bool readFromNetwork = true; + bool shouldDecrement = false; + try + { + Interlocked.Increment(ref _readingCount); + shouldDecrement = true; + readFromNetwork = !PartialPacketContainsCompletePacket(); + if (readFromNetwork) + { + syncReadPacket = ReadSyncOverAsync(stateObj.GetTimeoutRemaining(), out error); + } + else + { + error = TdsEnums.SNI_SUCCESS; + } + + Interlocked.Decrement(ref _readingCount); + shouldDecrement = false; + + if (TdsEnums.SNI_SUCCESS == error) + { + // We will end up letting the run method deal with the expected done:done_attn token stream. + stateObj.ProcessSniPacket(syncReadPacket, TdsEnums.SNI_SUCCESS); + return; + } + else + { + Debug.Assert(!readFromNetwork || !IsValidPacket(syncReadPacket), "unexpected syncReadPacket without corresponding SNIPacketRelease"); + fail = true; // Subsequent read failed, time to give up. + } + } + finally + { + if (shouldDecrement) + { + Interlocked.Decrement(ref _readingCount); + } + + if (readFromNetwork && !IsPacketEmpty(syncReadPacket)) + { + // Be sure to release packet, otherwise it will be leaked by native. + ReleasePacket(syncReadPacket); + } + } + } + else + { + if (_parser._loginWithFailover) + { + // For DbMirroring Failover during login, never break the connection, just close the TdsParser + _parser.Disconnect(); + } + else if ((_parser.State == TdsParserState.OpenNotLoggedIn) +#if NETFRAMEWORK + && (_parser.Connection.ConnectionOptions.MultiSubnetFailover || _parser.Connection.ConnectionOptions.TransparentNetworkIPResolution)) +#else + && (_parser.Connection.ConnectionOptions.MultiSubnetFailover)) +#endif + { + // For MultiSubnet Failover during login, never break the connection, just close the TdsParser + _parser.Disconnect(); + } + else + fail = true; // We aren't yet logged in - just fail. + } + } + } + + if (fail) + { + _parser.State = TdsParserState.Broken; // We failed subsequent read, we have to quit! + _parser.Connection.BreakConnection(); + } + } + else + { + // Caution: ProcessSNIError always returns a fatal error! + AddError(_parser.ProcessSNIError(stateObj)); + } + ThrowExceptionAndWarning(); + + AssertValidState(); + } + /// /// Checks to see if the underlying connection is still alive (used by connection pool resiliency) /// NOTE: This is not safe to do on a connection that is currently in use From 920601aeb4437b50b0a588fbe43003e86bd1a9a8 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 06:49:03 +0100 Subject: [PATCH 09/25] Merge GetSniPacket --- .../Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs | 5 ----- .../Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs | 5 ----- .../src/Microsoft/Data/SqlClient/TdsParserStateObject.cs | 5 +++++ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 47d836134a..5ed52f3200 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,11 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - private uint GetSniPacket(PacketHandle packet, ref uint dataSize) - { - return SniPacketGetData(packet, _inBuff, ref dataSize); - } - private bool TrySetBufferSecureStrings() { bool mustClearBuffer = false; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index c1e50fa308..a0985490ff 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,11 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - private uint GetSniPacket(PacketHandle packet, ref uint dataSize) - { - return SniPacketGetData(packet, _inBuff, ref dataSize); - } - private bool TrySetBufferSecureStrings() { bool mustClearBuffer = false; 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 828202f556..b41892db23 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3281,6 +3281,11 @@ private bool OnTimeoutCore(int expectedState, int targetState, bool asyncClose = return retval; } + private uint GetSniPacket(PacketHandle packet, ref uint dataSize) + { + return SniPacketGetData(packet, _inBuff, ref dataSize); + } + internal void ReadSni(TaskCompletionSource completion) { Debug.Assert(_networkPacketTaskSource == null || ((_asyncReadWithoutSnapshot) && (_networkPacketTaskSource.Task.IsCompleted)), "Pending async call or failed to replay snapshot when calling ReadSni"); From 2648e63c66ca88f1f81d705a019b5c7daa11ea3d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 07:22:01 +0100 Subject: [PATCH 10/25] Merge TrySetBufferSecureStrings --- .../SqlClient/TdsParserStateObject.netcore.cs | 41 ------------------ .../SqlClient/TdsParserStateObject.netfx.cs | 41 ------------------ .../Data/SqlClient/TdsParserStateObject.cs | 42 +++++++++++++++++++ 3 files changed, 42 insertions(+), 82 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 5ed52f3200..c6172560f1 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,47 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - private bool TrySetBufferSecureStrings() - { - bool mustClearBuffer = false; - - if (_securePasswords != null) - { - for (int i = 0; i < _securePasswords.Length; i++) - { - if (_securePasswords[i] != null) - { - IntPtr str = IntPtr.Zero; - try - { - str = Marshal.SecureStringToBSTR(_securePasswords[i]); - byte[] data = new byte[_securePasswords[i].Length * 2]; - Marshal.Copy(str, data, 0, _securePasswords[i].Length * 2); - if (!BitConverter.IsLittleEndian) - { - Span span = data.AsSpan(); - for (int ii = 0; ii < _securePasswords[i].Length * 2; ii += 2) - { - short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii)); - BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value); - } - } - TdsParserStaticMethods.ObfuscatePassword(data); - data.CopyTo(_outBuff, _securePasswordOffsetsInBuffer[i]); - - mustClearBuffer = true; - } - finally - { - Marshal.ZeroFreeBSTR(str); - } - } - } - } - - return mustClearBuffer; - } - public void ReadAsyncCallback(PacketHandle packet, uint error) => ReadAsyncCallback(IntPtr.Zero, packet, error); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index a0985490ff..d634d34592 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,47 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - private bool TrySetBufferSecureStrings() - { - bool mustClearBuffer = false; - - if (_securePasswords != null) - { - for (int i = 0; i < _securePasswords.Length; i++) - { - if (_securePasswords[i] != null) - { - IntPtr str = IntPtr.Zero; - try - { - str = Marshal.SecureStringToBSTR(_securePasswords[i]); - byte[] data = new byte[_securePasswords[i].Length * 2]; - Marshal.Copy(str, data, 0, _securePasswords[i].Length * 2); - if (!BitConverter.IsLittleEndian) - { - Span span = data.AsSpan(); - for (int ii = 0; ii < _securePasswords[i].Length * 2; ii += 2) - { - short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii)); - BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value); - } - } - TdsParserStaticMethods.ObfuscatePassword(data); - data.CopyTo(_outBuff, _securePasswordOffsetsInBuffer[i]); - - mustClearBuffer = true; - } - finally - { - Marshal.ZeroFreeBSTR(str); - } - } - } - } - - return mustClearBuffer; - } - public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) { // Key never used. 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 b41892db23..7f94af6838 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Security; using System.Security.Authentication; using System.Security.Cryptography; @@ -1386,6 +1387,47 @@ internal void SetBuffer(byte[] buffer, int inBytesUsed, int inBytesRead/*, [Call _inBytesRead = inBytesRead; } + private bool TrySetBufferSecureStrings() + { + bool mustClearBuffer = false; + + if (_securePasswords != null) + { + for (int i = 0; i < _securePasswords.Length; i++) + { + if (_securePasswords[i] != null) + { + IntPtr str = IntPtr.Zero; + try + { + str = Marshal.SecureStringToBSTR(_securePasswords[i]); + byte[] data = new byte[_securePasswords[i].Length * 2]; + Marshal.Copy(str, data, 0, _securePasswords[i].Length * 2); + if (!BitConverter.IsLittleEndian) + { + Span span = data.AsSpan(); + for (int ii = 0; ii < _securePasswords[i].Length * 2; ii += 2) + { + short value = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(ii)); + BinaryPrimitives.WriteInt16BigEndian(span.Slice(ii), value); + } + } + TdsParserStaticMethods.ObfuscatePassword(data); + data.CopyTo(_outBuff, _securePasswordOffsetsInBuffer[i]); + + mustClearBuffer = true; + } + finally + { + Marshal.ZeroFreeBSTR(str); + } + } + } + } + + return mustClearBuffer; + } + internal void NewBuffer(int size) { _inBuff = new byte[size]; From 5229c0ee1c7460dab44ecd68fd5cc4310d80e09c Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 07:27:46 +0100 Subject: [PATCH 11/25] netcore, netfx: sync debug assertion in ReadAsyncCallback --- .../Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs | 4 ++++ .../Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index c6172560f1..22e316c149 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -49,7 +49,11 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) bool processFinallyBlock = true; try { +#if NET Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback"); +#else + Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback"); +#endif if (_parser.MARSOn) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index d634d34592..569fea154b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -46,7 +46,11 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) bool processFinallyBlock = true; try { +#if NET + Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback"); +#else Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback"); +#endif if (_parser.MARSOn) { From 6810ba9913b89b9d095c4a603111b0e0aafe153b Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 07:29:30 +0100 Subject: [PATCH 12/25] Merge ReadAsyncCallback --- .../SqlClient/TdsParserStateObject.netcore.cs | 107 ------------------ .../SqlClient/TdsParserStateObject.netfx.cs | 104 ----------------- .../Data/SqlClient/TdsParserStateObject.cs | 107 ++++++++++++++++++ 3 files changed, 107 insertions(+), 211 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 22e316c149..abb5556c2a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,113 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - public void ReadAsyncCallback(PacketHandle packet, uint error) => - ReadAsyncCallback(IntPtr.Zero, packet, error); - - public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) - { - // Key never used. - // Note - it's possible that when native calls managed that an asynchronous exception - // could occur in the native->managed transition, which would - // have two impacts: - // 1) user event not called - // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due - // to the outstanding GCRoot until AppDomain.Unload. - // We live with the above for the time being due to the constraints of the current - // reliability infrastructure provided by the CLR. - - TaskCompletionSource source = _networkPacketTaskSource; -#if DEBUG - if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) - { - source = _realNetworkPacketTaskSource; - } -#endif - - // The mars physical connection can get a callback - // with a packet but no result after the connection is closed. - if (source == null && _parser._pMarsPhysicalConObj == this) - { - return; - } - - bool processFinallyBlock = true; - try - { -#if NET - Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback"); -#else - Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback"); -#endif - - if (_parser.MARSOn) - { - // Only take reset lock on MARS and Async. - CheckSetResetConnectionState(error, CallbackType.Read); - } - - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); - - // The timer thread may be unreliable under high contention scenarios. It cannot be - // assumed that the timeout has happened on the timer thread callback. Check the timeout - // synchronously and then call OnTimeoutSync to force an atomic change of state. - if (TimeoutHasExpired) - { - OnTimeoutSync(asyncClose: true); - } - - // try to change to the stopped state but only do so if currently in the running state - // and use cmpexch so that all changes out of the running state are atomic - int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running); - - // if the state is anything other than running then this query has reached an end so - // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing - if (_timeoutState != TimeoutState.Running) - { - _timeoutIdentityValue = 0; - } - - ProcessSniPacket(packet, error); - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task - int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle. - if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2)) - { - if (error == 0) - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source); - } - else - { - source.TrySetResult(null); - } - } - else - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source); - } - else - { - ReadAsyncCallbackCaptureException(source); - } - } - } - - AssertValidState(); - } - } - public void WriteAsyncCallback(PacketHandle packet, uint sniError) => WriteAsyncCallback(IntPtr.Zero, packet, sniError); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 569fea154b..7e3e50daa0 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,110 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) - { - // Key never used. - // Note - it's possible that when native calls managed that an asynchronous exception - // could occur in the native->managed transition, which would - // have two impacts: - // 1) user event not called - // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due - // to the outstanding GCRoot until AppDomain.Unload. - // We live with the above for the time being due to the constraints of the current - // reliability infrastructure provided by the CLR. - - TaskCompletionSource source = _networkPacketTaskSource; -#if DEBUG - if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) - { - source = _realNetworkPacketTaskSource; - } -#endif - - // The mars physical connection can get a callback - // with a packet but no result after the connection is closed. - if (source == null && _parser._pMarsPhysicalConObj == this) - { - return; - } - - bool processFinallyBlock = true; - try - { -#if NET - Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback"); -#else - Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback"); -#endif - - if (_parser.MARSOn) - { - // Only take reset lock on MARS and Async. - CheckSetResetConnectionState(error, CallbackType.Read); - } - - ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); - - // The timer thread may be unreliable under high contention scenarios. It cannot be - // assumed that the timeout has happened on the timer thread callback. Check the timeout - // synchronously and then call OnTimeoutSync to force an atomic change of state. - if (TimeoutHasExpired) - { - OnTimeoutSync(asyncClose: true); - } - - // try to change to the stopped state but only do so if currently in the running state - // and use cmpexch so that all changes out of the running state are atomic - int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running); - - // if the state is anything other than running then this query has reached an end so - // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing - if (_timeoutState != TimeoutState.Running) - { - _timeoutIdentityValue = 0; - } - - ProcessSniPacket(packet, error); - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task - int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle. - if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2)) - { - if (error == 0) - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source); - } - else - { - source.TrySetResult(null); - } - } - else - { - if (_executionContext != null) - { - ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source); - } - else - { - ReadAsyncCallbackCaptureException(source); - } - } - } - - AssertValidState(); - } - } - #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) 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 7f94af6838..688f3540aa 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3132,6 +3132,113 @@ internal void ReadSniSyncOverAsync() } } + public void ReadAsyncCallback(PacketHandle packet, uint error) => + ReadAsyncCallback(IntPtr.Zero, packet, error); + + public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) + { + // Key never used. + // Note - it's possible that when native calls managed that an asynchronous exception + // could occur in the native->managed transition, which would + // have two impacts: + // 1) user event not called + // 2) DecrementPendingCallbacks not called, which would mean this object would be leaked due + // to the outstanding GCRoot until AppDomain.Unload. + // We live with the above for the time being due to the constraints of the current + // reliability infrastructure provided by the CLR. + + TaskCompletionSource source = _networkPacketTaskSource; +#if DEBUG + if ((s_forcePendingReadsToWaitForUser) && (_realNetworkPacketTaskSource != null)) + { + source = _realNetworkPacketTaskSource; + } +#endif + + // The mars physical connection can get a callback + // with a packet but no result after the connection is closed. + if (source == null && _parser._pMarsPhysicalConObj == this) + { + return; + } + + bool processFinallyBlock = true; + try + { +#if NET + Debug.Assert((packet.Type == 0 && PartialPacketContainsCompletePacket()) || (CheckPacket(packet, source) && source != null), "AsyncResult null on callback"); +#else + Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback"); +#endif + + if (_parser.MARSOn) + { + // Only take reset lock on MARS and Async. + CheckSetResetConnectionState(error, CallbackType.Read); + } + + ChangeNetworkPacketTimeout(Timeout.Infinite, Timeout.Infinite); + + // The timer thread may be unreliable under high contention scenarios. It cannot be + // assumed that the timeout has happened on the timer thread callback. Check the timeout + // synchronously and then call OnTimeoutSync to force an atomic change of state. + if (TimeoutHasExpired) + { + OnTimeoutSync(asyncClose: true); + } + + // try to change to the stopped state but only do so if currently in the running state + // and use cmpexch so that all changes out of the running state are atomic + int previousState = Interlocked.CompareExchange(ref _timeoutState, TimeoutState.Stopped, TimeoutState.Running); + + // if the state is anything other than running then this query has reached an end so + // set the correlation _timeoutIdentityValue to 0 to prevent late callbacks executing + if (_timeoutState != TimeoutState.Running) + { + _timeoutIdentityValue = 0; + } + + ProcessSniPacket(packet, error); + } + catch (Exception e) + { + processFinallyBlock = ADP.IsCatchableExceptionType(e); + throw; + } + finally + { + // pendingCallbacks may be 2 after decrementing, this indicates that a fatal timeout is occurring, and therefore we shouldn't complete the task + int pendingCallbacks = DecrementPendingCallbacks(false); // may dispose of GC handle. + if ((processFinallyBlock) && (source != null) && (pendingCallbacks < 2)) + { + if (error == 0) + { + if (_executionContext != null) + { + ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source); + } + else + { + source.TrySetResult(null); + } + } + else + { + if (_executionContext != null) + { + ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source); + } + else + { + ReadAsyncCallbackCaptureException(source); + } + } + } + + AssertValidState(); + } + } + internal void OnConnectionClosed() { // the stateObj is not null, so the async invocation that registered this callback From 85a0a719aadd524812f8c116a010844dc1299a7d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:14:01 +0100 Subject: [PATCH 13/25] Merge WriteAsyncCallback --- .../SqlClient/TdsParserStateObject.netcore.cs | 86 ------------------ .../SqlClient/TdsParserStateObject.netfx.cs | 87 ------------------- .../Data/SqlClient/TdsParserStateObject.cs | 86 ++++++++++++++++++ 3 files changed, 86 insertions(+), 173 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index abb5556c2a..fe7e5f59df 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,92 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - public void WriteAsyncCallback(PacketHandle packet, uint sniError) => - WriteAsyncCallback(IntPtr.Zero, packet, sniError); - - public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) - { // Key never used. - RemovePacketFromPendingList(packet); - try - { - if (sniError != TdsEnums.SNI_SUCCESS) - { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); - try - { - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(asyncClose: true); - } - catch (Exception e) - { - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - writeCompletionSource.TrySetException(e); - } - else - { - _delayedWriteAsyncCallbackException = e; - - // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource - Interlocked.MemoryBarrier(); - - // Double check that _writeCompletionSource hasn't been created in the meantime - writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - writeCompletionSource.TrySetException(delayedException); - } - } - } - - return; - } - } - else - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - } - } - finally - { -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) - { - new Timer(obj => - { - Interlocked.Decrement(ref _asyncWriteCount); - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && writeCompletionSource != null) - { - writeCompletionSource.TrySetResult(null); - } - }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); - } - else - { -#else - { -#endif - Interlocked.Decrement(ref _asyncWriteCount); - } - } -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) - { - return; - } -#endif - TaskCompletionSource completionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && completionSource != null) - { - completionSource.TrySetResult(null); - } - } - ///////////////////////////////////////// // Network/Packet Writing & Processing // ///////////////////////////////////////// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 7e3e50daa0..9984a7a836 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,93 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { -#pragma warning disable 420 // a reference to a volatile field will not be treated as volatile - - public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) - { // Key never used. - RemovePacketFromPendingList(packet); - try - { - if (sniError != TdsEnums.SNI_SUCCESS) - { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); - try - { - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(asyncClose: true); - } - catch (Exception e) - { - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - writeCompletionSource.TrySetException(e); - } - else - { - _delayedWriteAsyncCallbackException = e; - - // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource - Interlocked.MemoryBarrier(); - - // Double check that _writeCompletionSource hasn't been created in the meantime - writeCompletionSource = _writeCompletionSource; - if (writeCompletionSource != null) - { - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - writeCompletionSource.TrySetException(delayedException); - } - } - } - - return; - } - } - else - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - } - } - finally - { -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) - { - new Timer(obj => - { - Interlocked.Decrement(ref _asyncWriteCount); - TaskCompletionSource writeCompletionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && writeCompletionSource != null) - { - writeCompletionSource.TrySetResult(null); - } - }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); - } - else - { -#else - { -#endif - Interlocked.Decrement(ref _asyncWriteCount); - } - } -#if DEBUG - if (SqlCommand.DebugForceAsyncWriteDelay > 0) - { - return; - } -#endif - TaskCompletionSource completionSource = _writeCompletionSource; - if (_asyncWriteCount == 0 && completionSource != null) - { - completionSource.TrySetResult(null); - } - } - -#pragma warning restore 420 - ///////////////////////////////////////// // Network/Packet Writing & Processing // ///////////////////////////////////////// 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 688f3540aa..07ff62db44 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -2851,6 +2851,92 @@ internal void ResetSecurePasswordsInformation() } } + public void WriteAsyncCallback(PacketHandle packet, uint sniError) => + WriteAsyncCallback(IntPtr.Zero, packet, sniError); + + public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) + { // Key never used. + RemovePacketFromPendingList(packet); + try + { + if (sniError != TdsEnums.SNI_SUCCESS) + { + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.WriteAsyncCallback | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); + try + { + AddError(_parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(asyncClose: true); + } + catch (Exception e) + { + TaskCompletionSource writeCompletionSource = _writeCompletionSource; + if (writeCompletionSource != null) + { + writeCompletionSource.TrySetException(e); + } + else + { + _delayedWriteAsyncCallbackException = e; + + // Ensure that _delayedWriteAsyncCallbackException is set before checking _writeCompletionSource + Interlocked.MemoryBarrier(); + + // Double check that _writeCompletionSource hasn't been created in the meantime + writeCompletionSource = _writeCompletionSource; + if (writeCompletionSource != null) + { + Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + writeCompletionSource.TrySetException(delayedException); + } + } + } + + return; + } + } + else + { + _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; + } + } + finally + { +#if DEBUG + if (SqlCommand.DebugForceAsyncWriteDelay > 0) + { + new Timer(obj => + { + Interlocked.Decrement(ref _asyncWriteCount); + TaskCompletionSource writeCompletionSource = _writeCompletionSource; + if (_asyncWriteCount == 0 && writeCompletionSource != null) + { + writeCompletionSource.TrySetResult(null); + } + }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); + } + else + { +#else + { +#endif + Interlocked.Decrement(ref _asyncWriteCount); + } + } +#if DEBUG + if (SqlCommand.DebugForceAsyncWriteDelay > 0) + { + return; + } +#endif + TaskCompletionSource completionSource = _writeCompletionSource; + if (_asyncWriteCount == 0 && completionSource != null) + { + completionSource.TrySetResult(null); + } + } + internal Task WaitForAccumulatedWrites() { // Checked for stored exceptions From b748a3a26aa9fe67b910d7719e493af2fc8ba7e5 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:18:22 +0100 Subject: [PATCH 14/25] Merge WritePacket --- .../SqlClient/TdsParserStateObject.netcore.cs | 81 ------------------- .../SqlClient/TdsParserStateObject.netfx.cs | 81 ------------------- .../Data/SqlClient/TdsParserStateObject.cs | 80 ++++++++++++++++++ 3 files changed, 80 insertions(+), 162 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index fe7e5f59df..b8231b47ab 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -19,87 +19,6 @@ internal abstract partial class TdsParserStateObject ///////////////////////////////////////// // Network/Packet Writing & Processing // ///////////////////////////////////////// - - // Dumps contents of buffer to SNI for network write. - internal Task WritePacket(byte flushMode, bool canAccumulate = false) - { - TdsParserState state = _parser.State; - if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } - - if ( - // This appears to be an optimization to avoid writing empty packets in 2005 - // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login - // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below - // So we need to avoid this check prior to login completing - state == TdsParserState.OpenLoggedIn - && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy - && _outBytesUsed == (_outputHeaderLen + - BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen))) - && _outputPacketCount == 0 - || _outBytesUsed == _outputHeaderLen - && _outputPacketCount == 0) - { - return null; - } - - byte status; - byte packetNumber = _outputPacketNumber; - - // Set Status byte based whether this is end of message or not - bool willCancel = (_cancelled) && (_parser._asyncWrite); - if (willCancel) - { - status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; - ResetPacketCounters(); - } - else if (TdsEnums.HARDFLUSH == flushMode) - { - status = TdsEnums.ST_EOM; - ResetPacketCounters(); - } - else if (TdsEnums.SOFTFLUSH == flushMode) - { - status = TdsEnums.ST_BATCH; - _outputPacketNumber++; - _outputPacketCount++; - } - else - { - status = TdsEnums.ST_EOM; - Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket"); - } - - _outBuff[0] = _outputMessageType; // Message Type - _outBuff[1] = status; - _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte - _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte - _outBuff[4] = 0; // channel - _outBuff[5] = 0; - _outBuff[6] = packetNumber; // packet - _outBuff[7] = 0; // window - - Task task = null; - _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible - - task = WriteSni(canAccumulate); - AssertValidState(); - - if (willCancel) - { - // If we have been canceled, then ensure that we write the ATTN packet as well -#if NET - task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket); -#else - task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection); -#endif - } - - return task; - } - #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 9984a7a836..d6aa71bc61 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -19,87 +19,6 @@ internal partial class TdsParserStateObject ///////////////////////////////////////// // Network/Packet Writing & Processing // ///////////////////////////////////////// - - // Dumps contents of buffer to SNI for network write. - internal Task WritePacket(byte flushMode, bool canAccumulate = false) - { - TdsParserState state = _parser.State; - if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } - - if ( - // This appears to be an optimization to avoid writing empty packets in 2005 - // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login - // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below - // So we need to avoid this check prior to login completing - state == TdsParserState.OpenLoggedIn - && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy - && _outBytesUsed == (_outputHeaderLen + - BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen))) - && _outputPacketCount == 0 - || _outBytesUsed == _outputHeaderLen - && _outputPacketCount == 0) - { - return null; - } - - byte status; - byte packetNumber = _outputPacketNumber; - - // Set Status byte based whether this is end of message or not - bool willCancel = (_cancelled) && (_parser._asyncWrite); - if (willCancel) - { - status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; - ResetPacketCounters(); - } - else if (TdsEnums.HARDFLUSH == flushMode) - { - status = TdsEnums.ST_EOM; - ResetPacketCounters(); - } - else if (TdsEnums.SOFTFLUSH == flushMode) - { - status = TdsEnums.ST_BATCH; - _outputPacketNumber++; - _outputPacketCount++; - } - else - { - status = TdsEnums.ST_EOM; - Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket"); - } - - _outBuff[0] = _outputMessageType; // Message Type - _outBuff[1] = status; - _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte - _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte - _outBuff[4] = 0; // channel - _outBuff[5] = 0; - _outBuff[6] = packetNumber; // packet - _outBuff[7] = 0; // window - - Task task = null; - _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible - - task = WriteSni(canAccumulate); - AssertValidState(); - - if (willCancel) - { - // If we have been canceled, then ensure that we write the ATTN packet as well -#if NET - task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket); -#else - task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection); -#endif - } - - return task; - } - #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) 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 07ff62db44..491dfcbc6b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -2937,6 +2937,86 @@ public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) } } + // Dumps contents of buffer to SNI for network write. + internal Task WritePacket(byte flushMode, bool canAccumulate = false) + { + TdsParserState state = _parser.State; + if ((state == TdsParserState.Closed) || (state == TdsParserState.Broken)) + { + throw ADP.ClosedConnectionError(); + } + + if ( + // This appears to be an optimization to avoid writing empty packets in 2005 + // However, since we don't know the version prior to login Is2005OrNewer was always false prior to login + // So removing the Is2005OrNewer check causes issues since the login packet happens to meet the rest of the conditions below + // So we need to avoid this check prior to login completing + state == TdsParserState.OpenLoggedIn + && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy + && _outBytesUsed == (_outputHeaderLen + + BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen))) + && _outputPacketCount == 0 + || _outBytesUsed == _outputHeaderLen + && _outputPacketCount == 0) + { + return null; + } + + byte status; + byte packetNumber = _outputPacketNumber; + + // Set Status byte based whether this is end of message or not + bool willCancel = (_cancelled) && (_parser._asyncWrite); + if (willCancel) + { + status = TdsEnums.ST_EOM | TdsEnums.ST_IGNORE; + ResetPacketCounters(); + } + else if (TdsEnums.HARDFLUSH == flushMode) + { + status = TdsEnums.ST_EOM; + ResetPacketCounters(); + } + else if (TdsEnums.SOFTFLUSH == flushMode) + { + status = TdsEnums.ST_BATCH; + _outputPacketNumber++; + _outputPacketCount++; + } + else + { + status = TdsEnums.ST_EOM; + Debug.Fail($"Unexpected argument {flushMode,-2:x2} to WritePacket"); + } + + _outBuff[0] = _outputMessageType; // Message Type + _outBuff[1] = status; + _outBuff[2] = (byte)(_outBytesUsed >> 8); // length - upper byte + _outBuff[3] = (byte)(_outBytesUsed & 0xff); // length - lower byte + _outBuff[4] = 0; // channel + _outBuff[5] = 0; + _outBuff[6] = packetNumber; // packet + _outBuff[7] = 0; // window + + Task task = null; + _parser.CheckResetConnection(this); // HAS SIDE EFFECTS - re-org at a later time if possible + + task = WriteSni(canAccumulate); + AssertValidState(); + + if (willCancel) + { + // If we have been canceled, then ensure that we write the ATTN packet as well +#if NET + task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket); +#else + task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection); +#endif + } + + return task; + } + internal Task WaitForAccumulatedWrites() { // Checked for stored exceptions From ed716ff16cefca6203a5e208f02846511d70833f Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:21:35 +0100 Subject: [PATCH 15/25] Merge SNIWritePacket --- .../SqlClient/TdsParserStateObject.netcore.cs | 128 ----------------- .../SqlClient/TdsParserStateObject.netfx.cs | 130 ------------------ .../Data/SqlClient/TdsParserStateObject.cs | 127 +++++++++++++++++ 3 files changed, 127 insertions(+), 258 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index b8231b47ab..50be020de6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -19,134 +19,6 @@ internal abstract partial class TdsParserStateObject ///////////////////////////////////////// // Network/Packet Writing & Processing // ///////////////////////////////////////// -#pragma warning disable 420 // a reference to a volatile field will not be treated as volatile - - private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) - { - // Check for a stored exception - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } - - Task task = null; - _writeCompletionSource = null; - PacketHandle packetPointer = EmptyReadPacket; - bool sync = !_parser._asyncWrite; - if (sync && _asyncWriteCount > 0) - { // for example, SendAttention while there are writes pending - Task waitForWrites = WaitForAccumulatedWrites(); - if (waitForWrites != null) - { - try - { - waitForWrites.Wait(); - } - catch (AggregateException ae) - { - throw ae.InnerException; - } - } - Debug.Assert(_asyncWriteCount == 0, "All async write should be finished"); - } - if (!sync) - { - // Add packet to the pending list (since the callback can happen any time after we call SNIWritePacket) - packetPointer = AddPacketToPendingList(packet); - } - - // Async operation completion may be delayed (success pending). - sniError = WritePacket(packet, sync); - - if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING) - { - Debug.Assert(!sync, "Completion should be handled in SniManagedWrapper"); - Interlocked.Increment(ref _asyncWriteCount); - Debug.Assert(_asyncWriteCount >= 0); - if (!canAccumulate) - { - // Create completion source (for callback to complete) - _writeCompletionSource = new TaskCompletionSource(); - task = _writeCompletionSource.Task; - - // Ensure that setting _writeCompletionSource completes before checking _delayedWriteAsyncCallbackException - Interlocked.MemoryBarrier(); - - // Check for a stored exception - delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } - - // If there are no outstanding writes, see if we can shortcut and return null - if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) - { - task = null; - } - } - } -#if DEBUG - else if (!sync && !canAccumulate && SqlCommand.DebugForceAsyncWriteDelay > 0) - { - // Executed synchronously - callback will not be called - TaskCompletionSource completion = new TaskCompletionSource(); - uint error = sniError; - new Timer(obj => - { - try - { - if (_parser.MARSOn) - { // Only take reset lock on MARS. - CheckSetResetConnectionState(error, CallbackType.Write); - } - - if (error != TdsEnums.SNI_SUCCESS) - { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)error); - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(false, asyncClose); - } - AssertValidState(); - completion.SetResult(null); - } - catch (Exception e) - { - completion.SetException(e); - } - }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); - task = completion.Task; - } -#endif - else - { - if (_parser.MARSOn) - { // Only take reset lock on MARS. - CheckSetResetConnectionState(sniError, CallbackType.Write); - } - - if (sniError == TdsEnums.SNI_SUCCESS) - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - - if (!sync) - { - // Since there will be no callback, remove the packet from the pending list - Debug.Assert(IsValidPacket(packetPointer), "Packet added to list has an invalid pointer, can not remove from pending list"); - RemovePacketFromPendingList(packetPointer); - } - } - else - { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(callerHasConnectionLock, asyncClose); - } - AssertValidState(); - } - return task; - } // Sends an attention signal - executing thread will consume attn. internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index d6aa71bc61..63ae3d61ac 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -19,136 +19,6 @@ internal partial class TdsParserStateObject ///////////////////////////////////////// // Network/Packet Writing & Processing // ///////////////////////////////////////// -#pragma warning disable 420 // a reference to a volatile field will not be treated as volatile - - private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) - { - // Check for a stored exception - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } - - Task task = null; - _writeCompletionSource = null; - PacketHandle packetPointer = EmptyReadPacket; - bool sync = !_parser._asyncWrite; - if (sync && _asyncWriteCount > 0) - { // for example, SendAttention while there are writes pending - Task waitForWrites = WaitForAccumulatedWrites(); - if (waitForWrites != null) - { - try - { - waitForWrites.Wait(); - } - catch (AggregateException ae) - { - throw ae.InnerException; - } - } - Debug.Assert(_asyncWriteCount == 0, "All async write should be finished"); - } - if (!sync) - { - // Add packet to the pending list (since the callback can happen any time after we call SNIWritePacket) - packetPointer = AddPacketToPendingList(packet); - } - - // Async operation completion may be delayed (success pending). - sniError = WritePacket(packet, sync); - - if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING) - { - Debug.Assert(!sync, "Completion should be handled in SniManagedWrapper"); - Interlocked.Increment(ref _asyncWriteCount); - Debug.Assert(_asyncWriteCount >= 0); - if (!canAccumulate) - { - // Create completion source (for callback to complete) - _writeCompletionSource = new TaskCompletionSource(); - task = _writeCompletionSource.Task; - - // Ensure that setting _writeCompletionSource completes before checking _delayedWriteAsyncCallbackException - Interlocked.MemoryBarrier(); - - // Check for a stored exception - delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } - - // If there are no outstanding writes, see if we can shortcut and return null - if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) - { - task = null; - } - } - } -#if DEBUG - else if (!sync && !canAccumulate && SqlCommand.DebugForceAsyncWriteDelay > 0) - { - // Executed synchronously - callback will not be called - TaskCompletionSource completion = new TaskCompletionSource(); - uint error = sniError; - new Timer(obj => - { - try - { - if (_parser.MARSOn) - { // Only take reset lock on MARS. - CheckSetResetConnectionState(error, CallbackType.Write); - } - - if (error != TdsEnums.SNI_SUCCESS) - { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)error); - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(false, asyncClose); - } - AssertValidState(); - completion.SetResult(null); - } - catch (Exception e) - { - completion.SetException(e); - } - }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); - task = completion.Task; - } -#endif - else - { - if (_parser.MARSOn) - { // Only take reset lock on MARS. - CheckSetResetConnectionState(sniError, CallbackType.Write); - } - - if (sniError == TdsEnums.SNI_SUCCESS) - { - _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; - - if (!sync) - { - // Since there will be no callback, remove the packet from the pending list - Debug.Assert(IsValidPacket(packetPointer), "Packet added to list has an invalid pointer, can not remove from pending list"); - RemovePacketFromPendingList(packetPointer); - } - } - else - { - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); - AddError(_parser.ProcessSNIError(this)); - ThrowExceptionAndWarning(callerHasConnectionLock, asyncClose); - } - AssertValidState(); - } - return task; - } - -#pragma warning restore 420 // Sends an attention signal - executing thread will consume attn. internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) 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 491dfcbc6b..3d7a3902a0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3017,6 +3017,133 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) return task; } + private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) + { + // Check for a stored exception + Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + throw delayedException; + } + + Task task = null; + _writeCompletionSource = null; + PacketHandle packetPointer = EmptyReadPacket; + bool sync = !_parser._asyncWrite; + if (sync && _asyncWriteCount > 0) + { // for example, SendAttention while there are writes pending + Task waitForWrites = WaitForAccumulatedWrites(); + if (waitForWrites != null) + { + try + { + waitForWrites.Wait(); + } + catch (AggregateException ae) + { + throw ae.InnerException; + } + } + Debug.Assert(_asyncWriteCount == 0, "All async write should be finished"); + } + if (!sync) + { + // Add packet to the pending list (since the callback can happen any time after we call SNIWritePacket) + packetPointer = AddPacketToPendingList(packet); + } + + // Async operation completion may be delayed (success pending). + sniError = WritePacket(packet, sync); + + if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING) + { + Debug.Assert(!sync, "Completion should be handled in SniManagedWrapper"); + Interlocked.Increment(ref _asyncWriteCount); + Debug.Assert(_asyncWriteCount >= 0); + if (!canAccumulate) + { + // Create completion source (for callback to complete) + _writeCompletionSource = new TaskCompletionSource(); + task = _writeCompletionSource.Task; + + // Ensure that setting _writeCompletionSource completes before checking _delayedWriteAsyncCallbackException + Interlocked.MemoryBarrier(); + + // Check for a stored exception + delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + throw delayedException; + } + + // If there are no outstanding writes, see if we can shortcut and return null + if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) + { + task = null; + } + } + } +#if DEBUG + else if (!sync && !canAccumulate && SqlCommand.DebugForceAsyncWriteDelay > 0) + { + // Executed synchronously - callback will not be called + TaskCompletionSource completion = new TaskCompletionSource(); + uint error = sniError; + new Timer(obj => + { + try + { + if (_parser.MARSOn) + { // Only take reset lock on MARS. + CheckSetResetConnectionState(error, CallbackType.Write); + } + + if (error != TdsEnums.SNI_SUCCESS) + { + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)error); + AddError(_parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(false, asyncClose); + } + AssertValidState(); + completion.SetResult(null); + } + catch (Exception e) + { + completion.SetException(e); + } + }, null, SqlCommand.DebugForceAsyncWriteDelay, Timeout.Infinite); + task = completion.Task; + } +#endif + else + { + if (_parser.MARSOn) + { // Only take reset lock on MARS. + CheckSetResetConnectionState(sniError, CallbackType.Write); + } + + if (sniError == TdsEnums.SNI_SUCCESS) + { + _lastSuccessfulIOTimer._value = DateTime.UtcNow.Ticks; + + if (!sync) + { + // Since there will be no callback, remove the packet from the pending list + Debug.Assert(IsValidPacket(packetPointer), "Packet added to list has an invalid pointer, can not remove from pending list"); + RemovePacketFromPendingList(packetPointer); + } + } + else + { + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SNIWritePacket | Info | State Object Id {0}, Write async returned error code {1}", _objectID, (int)sniError); + AddError(_parser.ProcessSNIError(this)); + ThrowExceptionAndWarning(callerHasConnectionLock, asyncClose); + } + AssertValidState(); + } + return task; + } + internal Task WaitForAccumulatedWrites() { // Checked for stored exceptions From edf61c76933defbb75efdca33109cb2159c77632 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:22:52 +0100 Subject: [PATCH 16/25] Merge WriteSni --- .../SqlClient/TdsParserStateObject.netcore.cs | 61 ------------------- .../SqlClient/TdsParserStateObject.netfx.cs | 61 ------------------- .../Data/SqlClient/TdsParserStateObject.cs | 61 +++++++++++++++++++ 3 files changed, 61 insertions(+), 122 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 50be020de6..2012e6f27d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -92,67 +92,6 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa } } - private Task WriteSni(bool canAccumulate) - { - // Prepare packet, and write to packet. - PacketHandle packet = GetResetWritePacket(_outBytesUsed); - bool mustClearBuffer = TrySetBufferSecureStrings(); - - SetPacketData(packet, _outBuff, _outBytesUsed); - if (mustClearBuffer) - { - _outBuff.AsSpan(0, _outBytesUsed).Clear(); - } - - Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock"); - Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true); - - // Check to see if the timeout has occurred. This time out code is special case code to allow BCP writes to timeout. Eventually we should make all writes timeout. - if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining()) - { - _parser.Connection.ThreadHasParserLockForClose = true; - try - { - Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - _bulkCopyWriteTimeout = true; - SendAttention(); - _parser.ProcessPendingAck(this); - ThrowExceptionAndWarning(); - } - finally - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - } - - // Special case logic for encryption removal. - if (_parser.State == TdsParserState.OpenNotLoggedIn && - _parser.EncryptionOptions == EncryptionOptions.LOGIN) - { - // If no error occurred, and we are Open but not logged in, and - // our encryptionOption state is login, remove the SSL Provider. - // We only need encrypt the very first packet of the login message to the server. - - // We wanted to encrypt entire login channel, but there is - // currently no mechanism to communicate this. Removing encryption post 1st packet - // is a hard-coded agreement between client and server. We need some mechanism or - // common change to be able to make this change in a non-breaking fashion. - _parser.RemoveEncryption(); // Remove the SSL Provider. - _parser.EncryptionOptions = EncryptionOptions.OFF; // Turn encryption off. - - // Since this packet was associated with encryption, dispose and re-create. - ClearAllWritePackets(); - } - - SniWriteStatisticsAndTracing(); - - ResetBuffer(); - - AssertValidState(); - return task; - } - ////////////////////////////////////////////// // Statistics, Tracing, and related methods // ////////////////////////////////////////////// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 63ae3d61ac..f0697f28a8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -92,67 +92,6 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa } } - private Task WriteSni(bool canAccumulate) - { - // Prepare packet, and write to packet. - PacketHandle packet = GetResetWritePacket(_outBytesUsed); - bool mustClearBuffer = TrySetBufferSecureStrings(); - - SetPacketData(packet, _outBuff, _outBytesUsed); - if (mustClearBuffer) - { - _outBuff.AsSpan(0, _outBytesUsed).Clear(); - } - - Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock"); - Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true); - - // Check to see if the timeout has occurred. This time out code is special case code to allow BCP writes to timeout. Eventually we should make all writes timeout. - if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining()) - { - _parser.Connection.ThreadHasParserLockForClose = true; - try - { - Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); - AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); - _bulkCopyWriteTimeout = true; - SendAttention(); - _parser.ProcessPendingAck(this); - ThrowExceptionAndWarning(); - } - finally - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - } - - // Special case logic for encryption removal. - if (_parser.State == TdsParserState.OpenNotLoggedIn && - (_parser.EncryptionOptions & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN) - { - // If no error occurred, and we are Open but not logged in, and - // our encryptionOption state is login, remove the SSL Provider. - // We only need encrypt the very first packet of the login message to the server. - - // We wanted to encrypt entire login channel, but there is - // currently no mechanism to communicate this. Removing encryption post 1st packet - // is a hard-coded agreement between client and server. We need some mechanism or - // common change to be able to make this change in a non-breaking fashion. - _parser.RemoveEncryption(); // Remove the SSL Provider. - _parser.EncryptionOptions = EncryptionOptions.OFF | (_parser.EncryptionOptions & ~EncryptionOptions.OPTIONS_MASK); // Turn encryption off. - - // Since this packet was associated with encryption, dispose and re-create. - ClearAllWritePackets(); - } - - SniWriteStatisticsAndTracing(); - - ResetBuffer(); - - AssertValidState(); - return task; - } - ////////////////////////////////////////////// // Statistics, Tracing, and related methods // ////////////////////////////////////////////// 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 3d7a3902a0..f435bc3de3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3144,6 +3144,67 @@ private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccu return task; } + private Task WriteSni(bool canAccumulate) + { + // Prepare packet, and write to packet. + PacketHandle packet = GetResetWritePacket(_outBytesUsed); + bool mustClearBuffer = TrySetBufferSecureStrings(); + + SetPacketData(packet, _outBuff, _outBytesUsed); + if (mustClearBuffer) + { + _outBuff.AsSpan(0, _outBytesUsed).Clear(); + } + + Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock"); + Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true); + + // Check to see if the timeout has occurred. This time out code is special case code to allow BCP writes to timeout. Eventually we should make all writes timeout. + if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining()) + { + _parser.Connection.ThreadHasParserLockForClose = true; + try + { + Debug.Assert(_parser.Connection != null, "SqlConnectionInternalTds handler can not be null at this point."); + AddError(new SqlError(TdsEnums.TIMEOUT_EXPIRED, 0x00, TdsEnums.MIN_ERROR_CLASS, _parser.Server, _parser.Connection.TimeoutErrorInternal.GetErrorMessage(), "", 0, TdsEnums.SNI_WAIT_TIMEOUT)); + _bulkCopyWriteTimeout = true; + SendAttention(); + _parser.ProcessPendingAck(this); + ThrowExceptionAndWarning(); + } + finally + { + _parser.Connection.ThreadHasParserLockForClose = false; + } + } + + // Special case logic for encryption removal. + if (_parser.State == TdsParserState.OpenNotLoggedIn && + (_parser.EncryptionOptions & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN) + { + // If no error occurred, and we are Open but not logged in, and + // our encryptionOption state is login, remove the SSL Provider. + // We only need encrypt the very first packet of the login message to the server. + + // We wanted to encrypt entire login channel, but there is + // currently no mechanism to communicate this. Removing encryption post 1st packet + // is a hard-coded agreement between client and server. We need some mechanism or + // common change to be able to make this change in a non-breaking fashion. + _parser.RemoveEncryption(); // Remove the SSL Provider. + _parser.EncryptionOptions = EncryptionOptions.OFF | (_parser.EncryptionOptions & ~EncryptionOptions.OPTIONS_MASK); // Turn encryption off. + + // Since this packet was associated with encryption, dispose and re-create. + ClearAllWritePackets(); + } + + SniWriteStatisticsAndTracing(); + + ResetBuffer(); + + AssertValidState(); + return task; + } + internal Task WaitForAccumulatedWrites() { // Checked for stored exceptions From cafbd0270a12d5c04fb802ca7e147074625bbc48 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:25:30 +0100 Subject: [PATCH 17/25] Merge SendAttention --- .../SqlClient/TdsParserStateObject.netcore.cs | 76 ------------------- .../SqlClient/TdsParserStateObject.netfx.cs | 76 ------------------- .../Data/SqlClient/TdsParserStateObject.cs | 72 ++++++++++++++++++ 3 files changed, 72 insertions(+), 152 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 2012e6f27d..20a5ce0c11 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,82 +16,6 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - ///////////////////////////////////////// - // Network/Packet Writing & Processing // - ///////////////////////////////////////// - - // Sends an attention signal - executing thread will consume attn. - internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) - { - if (!_attentionSent) - { - // Dumps contents of buffer to OOB write (currently only used for - // attentions. There is no body for this message - // Doesn't touch this._outBytesUsed - if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken) - { - return; - } - - PacketHandle attnPacket = CreateAndSetAttentionPacket(); - - try - { - // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close - // Set _attentionSending to true before sending attention and reset after setting _attentionSent - // This prevents a race condition between receiving the attention ACK and setting _attentionSent - _attentionSending = true; -#if DEBUG - if (!s_skipSendAttention) - { -#endif - // Take lock and send attention - bool releaseLock = false; - if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose)) - { - releaseLock = true; - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false); - _parser.Connection.ThreadHasParserLockForClose = true; - } - try - { - // Check again (just in case the connection was closed while we were waiting) - if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken) - { - return; - } - - _parser._asyncWrite = false; // stop async write - SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose); - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID); - } - finally - { - if (releaseLock) - { - _parser.Connection.ThreadHasParserLockForClose = false; - _parser.Connection._parserLock.Release(); - } - } -#if DEBUG - } -#endif - - SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds. - _attentionSent = true; - } - finally - { - _attentionSending = false; - } - - SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.SendAttention | INFO | ADV | State Object Id {0}, Packet sent. Out Buffer: {1}, Out Bytes Used: {2}", _objectID, _outBuff, _outBytesUsed); - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Attention sent to the server.", _objectID); - - AssertValidState(); - } - } - ////////////////////////////////////////////// // Statistics, Tracing, and related methods // ////////////////////////////////////////////// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index f0697f28a8..9f4099c0f7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,82 +16,6 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - ///////////////////////////////////////// - // Network/Packet Writing & Processing // - ///////////////////////////////////////// - - // Sends an attention signal - executing thread will consume attn. - internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) - { - if (!_attentionSent) - { - // Dumps contents of buffer to OOB write (currently only used for - // attentions. There is no body for this message - // Doesn't touch this._outBytesUsed - if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken) - { - return; - } - - PacketHandle attnPacket = CreateAndSetAttentionPacket(); - - try - { - // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close - // Set _attentionSending to true before sending attention and reset after setting _attentionSent - // This prevents a race condition between receiving the attention ACK and setting _attentionSent - _attentionSending = true; -#if DEBUG - if (!s_skipSendAttention) - { -#endif - // Take lock and send attention - bool releaseLock = false; - if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose)) - { - releaseLock = true; - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false); - _parser.Connection.ThreadHasParserLockForClose = true; - } - try - { - // Check again (just in case the connection was closed while we were waiting) - if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken) - { - return; - } - - _parser._asyncWrite = false; // stop async write - SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose); - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID); - } - finally - { - if (releaseLock) - { - _parser.Connection.ThreadHasParserLockForClose = false; - _parser.Connection._parserLock.Release(); - } - } -#if DEBUG - } -#endif - - SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds. - _attentionSent = true; - } - finally - { - _attentionSending = false; - } - - SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.SendAttention | INFO | ADV | State Object Id {0}, Packet sent. Out Buffer: {1}, Out Bytes Used: {2}", _objectID, _outBuff, _outBytesUsed); - SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Attention sent to the server.", _objectID); - - AssertValidState(); - } - } - ////////////////////////////////////////////// // Statistics, Tracing, and related methods // ////////////////////////////////////////////// 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 f435bc3de3..bfb2872832 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3283,6 +3283,78 @@ private void CancelWritePacket() } } + // Sends an attention signal - executing thread will consume attn. + internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) + { + if (!_attentionSent) + { + // Dumps contents of buffer to OOB write (currently only used for + // attentions. There is no body for this message + // Doesn't touch this._outBytesUsed + if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken) + { + return; + } + + PacketHandle attnPacket = CreateAndSetAttentionPacket(); + + try + { + // Dev11 #344723: SqlClient stress test suspends System_Data!Tcp::ReadSync via a call to SqlDataReader::Close + // Set _attentionSending to true before sending attention and reset after setting _attentionSent + // This prevents a race condition between receiving the attention ACK and setting _attentionSent + _attentionSending = true; +#if DEBUG + if (!s_skipSendAttention) + { +#endif + // Take lock and send attention + bool releaseLock = false; + if ((mustTakeWriteLock) && (!_parser.Connection.ThreadHasParserLockForClose)) + { + releaseLock = true; + _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false); + _parser.Connection.ThreadHasParserLockForClose = true; + } + try + { + // Check again (just in case the connection was closed while we were waiting) + if (_parser.State == TdsParserState.Closed || _parser.State == TdsParserState.Broken) + { + return; + } + + _parser._asyncWrite = false; // stop async write + SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID); + } + finally + { + if (releaseLock) + { + _parser.Connection.ThreadHasParserLockForClose = false; + _parser.Connection._parserLock.Release(); + } + } +#if DEBUG + } +#endif + + SetTimeoutSeconds(AttentionTimeoutSeconds); // Initialize new attention timeout of 5 seconds. + _attentionSent = true; + } + finally + { + _attentionSending = false; + } + + SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParserStateObject.SendAttention | INFO | ADV | State Object Id {0}, Packet sent. Out Buffer: {1}, Out Bytes Used: {2}", _objectID, _outBuff, _outBytesUsed); + SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Attention sent to the server.", _objectID); + + AssertValidState(); + } + } + ///////////////////////////////////////// // Network/Packet Reading & Processing // ///////////////////////////////////////// From 3defa6fc954386d04005b1059441e8a17e9d915c Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:27:48 +0100 Subject: [PATCH 18/25] netfx, netcore: sync SniWriteStatisticsAndTracing --- .../SqlClient/TdsParserStateObject.netcore.cs | 33 +++++++++++++++++++ .../SqlClient/TdsParserStateObject.netfx.cs | 2 ++ 2 files changed, 35 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 20a5ce0c11..90f9782500 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -29,6 +29,39 @@ private void SniWriteStatisticsAndTracing() statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed); statistics.RequestNetworkServerTimer(); } +#if NETFRAMEWORK + if (SqlClientEventSource.Log.IsAdvancedTraceOn()) + { + // If we have tracePassword variables set, we are flushing TDSLogin and so we need to + // blank out password in buffer. Buffer has already been sent to netlib, so no danger + // of losing info. + if (_tracePasswordOffset != 0) + { + for (int i = _tracePasswordOffset; i < _tracePasswordOffset + + _tracePasswordLength; i++) + { + _outBuff[i] = 0; + } + + // Reset state. + _tracePasswordOffset = 0; + _tracePasswordLength = 0; + } + if (_traceChangePasswordOffset != 0) + { + for (int i = _traceChangePasswordOffset; i < _traceChangePasswordOffset + + _traceChangePasswordLength; i++) + { + _outBuff[i] = 0; + } + + // Reset state. + _traceChangePasswordOffset = 0; + _traceChangePasswordLength = 0; + } + } + SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed); +#endif } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 9f4099c0f7..9cb2565f01 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -29,6 +29,7 @@ private void SniWriteStatisticsAndTracing() statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed); statistics.RequestNetworkServerTimer(); } +#if NETFRAMEWORK if (SqlClientEventSource.Log.IsAdvancedTraceOn()) { // If we have tracePassword variables set, we are flushing TDSLogin and so we need to @@ -60,6 +61,7 @@ private void SniWriteStatisticsAndTracing() } } SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed); +#endif } } } From cfd5d5b27f89722fa9813b280a7cc7d5141e86de Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:28:39 +0100 Subject: [PATCH 19/25] Merge SniWriteStatisticsAndTracing --- .../SqlClient/TdsParserStateObject.netcore.cs | 47 ------------------- .../SqlClient/TdsParserStateObject.netfx.cs | 47 ------------------- .../Data/SqlClient/TdsParserStateObject.cs | 44 +++++++++++++++++ 3 files changed, 44 insertions(+), 94 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 90f9782500..b26c322a86 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -16,52 +16,5 @@ namespace Microsoft.Data.SqlClient { internal abstract partial class TdsParserStateObject { - ////////////////////////////////////////////// - // Statistics, Tracing, and related methods // - ////////////////////////////////////////////// - - private void SniWriteStatisticsAndTracing() - { - SqlStatistics statistics = _parser.Statistics; - if (statistics != null) - { - statistics.SafeIncrement(ref statistics._buffersSent); - statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed); - statistics.RequestNetworkServerTimer(); - } -#if NETFRAMEWORK - if (SqlClientEventSource.Log.IsAdvancedTraceOn()) - { - // If we have tracePassword variables set, we are flushing TDSLogin and so we need to - // blank out password in buffer. Buffer has already been sent to netlib, so no danger - // of losing info. - if (_tracePasswordOffset != 0) - { - for (int i = _tracePasswordOffset; i < _tracePasswordOffset + - _tracePasswordLength; i++) - { - _outBuff[i] = 0; - } - - // Reset state. - _tracePasswordOffset = 0; - _tracePasswordLength = 0; - } - if (_traceChangePasswordOffset != 0) - { - for (int i = _traceChangePasswordOffset; i < _traceChangePasswordOffset + - _traceChangePasswordLength; i++) - { - _outBuff[i] = 0; - } - - // Reset state. - _traceChangePasswordOffset = 0; - _traceChangePasswordLength = 0; - } - } - SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed); -#endif - } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 9cb2565f01..884aba2cd4 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -16,52 +16,5 @@ namespace Microsoft.Data.SqlClient { internal partial class TdsParserStateObject { - ////////////////////////////////////////////// - // Statistics, Tracing, and related methods // - ////////////////////////////////////////////// - - private void SniWriteStatisticsAndTracing() - { - SqlStatistics statistics = _parser.Statistics; - if (statistics != null) - { - statistics.SafeIncrement(ref statistics._buffersSent); - statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed); - statistics.RequestNetworkServerTimer(); - } -#if NETFRAMEWORK - if (SqlClientEventSource.Log.IsAdvancedTraceOn()) - { - // If we have tracePassword variables set, we are flushing TDSLogin and so we need to - // blank out password in buffer. Buffer has already been sent to netlib, so no danger - // of losing info. - if (_tracePasswordOffset != 0) - { - for (int i = _tracePasswordOffset; i < _tracePasswordOffset + - _tracePasswordLength; i++) - { - _outBuff[i] = 0; - } - - // Reset state. - _tracePasswordOffset = 0; - _tracePasswordLength = 0; - } - if (_traceChangePasswordOffset != 0) - { - for (int i = _traceChangePasswordOffset; i < _traceChangePasswordOffset + - _traceChangePasswordLength; i++) - { - _outBuff[i] = 0; - } - - // Reset state. - _traceChangePasswordOffset = 0; - _traceChangePasswordLength = 0; - } - } - SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed); -#endif - } } } 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 bfb2872832..7fbb546541 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -2568,6 +2568,50 @@ private void SniReadStatisticsAndTracing() } } + private void SniWriteStatisticsAndTracing() + { + SqlStatistics statistics = _parser.Statistics; + if (statistics != null) + { + statistics.SafeIncrement(ref statistics._buffersSent); + statistics.SafeAdd(ref statistics._bytesSent, _outBytesUsed); + statistics.RequestNetworkServerTimer(); + } +#if NETFRAMEWORK + if (SqlClientEventSource.Log.IsAdvancedTraceOn()) + { + // If we have tracePassword variables set, we are flushing TDSLogin and so we need to + // blank out password in buffer. Buffer has already been sent to netlib, so no danger + // of losing info. + if (_tracePasswordOffset != 0) + { + for (int i = _tracePasswordOffset; i < _tracePasswordOffset + + _tracePasswordLength; i++) + { + _outBuff[i] = 0; + } + + // Reset state. + _tracePasswordOffset = 0; + _tracePasswordLength = 0; + } + if (_traceChangePasswordOffset != 0) + { + for (int i = _traceChangePasswordOffset; i < _traceChangePasswordOffset + + _traceChangePasswordLength; i++) + { + _outBuff[i] = 0; + } + + // Reset state. + _traceChangePasswordOffset = 0; + _traceChangePasswordLength = 0; + } + } + SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed); +#endif + } + [Conditional("DEBUG")] private void AssertValidState() { From b2a69969fa93c54566615cd6f785f61413a91a6c Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:30:50 +0100 Subject: [PATCH 20/25] Remove now-unused target-specific TdsParserStateObject.cs files --- .../src/Microsoft.Data.SqlClient.csproj | 1 - .../SqlClient/TdsParserStateObject.netcore.cs | 20 ------------------- .../netfx/src/Microsoft.Data.SqlClient.csproj | 1 - .../SqlClient/TdsParserStateObject.netfx.cs | 20 ------------------- 4 files changed, 42 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 5d06d69703..4853409443 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -837,7 +837,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs deleted file mode 100644 index b26c322a86..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Buffers.Binary; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; - -namespace Microsoft.Data.SqlClient -{ - internal abstract partial class TdsParserStateObject - { - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index fbc08994c9..aa2e92fe52 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1008,7 +1008,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs deleted file mode 100644 index 884aba2cd4..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Buffers.Binary; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Security; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; - -namespace Microsoft.Data.SqlClient -{ - internal partial class TdsParserStateObject - { - } -} From 70e34cd8eb4242b1f345e7519b596468631b9f49 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:35:00 +0100 Subject: [PATCH 21/25] TdsParser: Move variable to shared file --- .../src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs | 2 -- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs index 6226f958a5..f9259df414 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs @@ -11,8 +11,6 @@ namespace Microsoft.Data.SqlClient internal partial class TdsParser { - private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - internal void ProcessSSPI(int receivedLength) { Debug.Assert(_authenticationProvider is not null); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index 8f994271a8..c6dae1a007 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -29,6 +29,7 @@ using Microsoft.Data.SqlClient.DataClassification; using Microsoft.Data.SqlClient.LocalDb; using Microsoft.Data.SqlClient.Server; +using Microsoft.Data.SqlClient.Utilities; #if NETFRAMEWORK using Microsoft.Data.SqlTypes; #endif @@ -83,6 +84,7 @@ internal sealed partial class TdsParser private int _defaultLCID; + private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); internal Encoding _defaultEncoding = null; // for sql character data private static EncryptionOptions s_sniSupportedEncryptionOption = TdsParserStateObjectFactory.Singleton.EncryptionOptions; From ea20e46488b5b80300e3fe0cc5d888e8e3886431 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:36:45 +0100 Subject: [PATCH 22/25] TdsParser: Move TdsLogin --- .../Data/SqlClient/TdsParser.SSPI.cs | 159 ------------------ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 159 ++++++++++++++++++ 2 files changed, 159 insertions(+), 159 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs index f9259df414..2775f4a2f1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs @@ -62,164 +62,5 @@ internal void ProcessSSPI(int receivedLength) } #nullable disable - - internal void TdsLogin( - SqlLogin rec, - TdsEnums.FeatureExtension requestedFeatures, - SessionData recoverySessionData, - FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData, - SqlConnectionEncryptOption encrypt) - { - _physicalStateObj.SetTimeoutSeconds(rec.timeout); - - Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request"); - Debug.Assert(TdsEnums.MAXLEN_HOSTNAME >= rec.hostName.Length, "_workstationId.Length exceeds the max length for this value"); - - Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option."); - Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth"); - Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request"); - Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData."); - - Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_CLIENTID >= rec.userName.Length), "_userID.Length exceeds the max length for this value"); - Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTID >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value"); - - Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.password.Length), "_password.Length exceeds the max length for this value"); - Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value"); - - Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password"); - Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password"); - Debug.Assert(TdsEnums.MAXLEN_APPNAME >= rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_SERVERNAME >= rec.serverName.Length, "_dataSource.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_LANGUAGE >= rec.language.Length, "_currentLanguage .Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_DATABASE >= rec.database.Length, "_initialCatalog.Length exceeds the max length for this value"); - Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE >= rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value"); - - Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); - _connHandler!.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); - _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth); - - // get the password up front to use in sspi logic below - byte[] encryptedPassword = null; - byte[] encryptedChangePassword = null; - int encryptedPasswordLengthInBytes; - int encryptedChangePasswordLengthInBytes; - bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None); - - string userName; - - if (rec.credential != null) - { - userName = rec.credential.UserId; - encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2; - } - else - { - userName = rec.userName; - encryptedPassword = TdsParserStaticMethods.ObfuscatePassword(rec.password); - encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte - } - - if (rec.newSecurePassword != null) - { - encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2; - } - else - { - encryptedChangePassword = TdsParserStaticMethods.ObfuscatePassword(rec.newPassword); - encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length; - } - - // set the message type - _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7; - - // length in bytes - int length = TdsEnums.SQL2005_LOG_REC_FIXED_LEN; - - string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME; - Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec"); - - // add up variable-len portions (multiply by 2 for byte len of char strings) - // - checked - { - length += (rec.hostName.Length + rec.applicationName.Length + - rec.serverName.Length + clientInterfaceName.Length + - rec.language.Length + rec.database.Length + - rec.attachDBFilename.Length) * 2; - if (useFeatureExt) - { - length += 4; - } - } - - // allocate memory for SSPI variables - ArrayBufferWriter sspiWriter = null; - - try - { - // only add lengths of password and username if not using SSPI or requesting federated authentication info - if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) - { - checked - { - length += (userName.Length * 2) + encryptedPasswordLengthInBytes - + encryptedChangePasswordLengthInBytes; - } - } - else - { - if (rec.useSSPI) - { - sspiWriter = ObjectPools.BufferWriter.Rent(); - - // Call helper function for SSPI data and actual length. - // Since we don't have SSPI data from the server, send null for the - // byte[] buffer and 0 for the int length. - Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'"); - _physicalStateObj.SniContext = SniContext.Snix_LoginSspi; - _authenticationProvider.WriteSSPIContext(ReadOnlySpan.Empty, sspiWriter); - - _physicalStateObj.SniContext = SniContext.Snix_Login; - - checked - { - length += (int)sspiWriter.WrittenCount; - } - } - } - - int feOffset = length; - // calculate and reserve the required bytes for the featureEx - length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); - - WriteLoginData(rec, - requestedFeatures, - recoverySessionData, - fedAuthFeatureExtensionData, - encrypt, - encryptedPassword, - encryptedChangePassword, - encryptedPasswordLengthInBytes, - encryptedChangePasswordLengthInBytes, - useFeatureExt, - userName, - length, - feOffset, - clientInterfaceName, - sspiWriter is { } ? sspiWriter.WrittenSpan : ReadOnlySpan.Empty); - } - finally - { - if (sspiWriter is not null) - { - ObjectPools.BufferWriter.Return(sspiWriter); - } - } - - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj.ResetSecurePasswordsInformation(); // Password information is needed only from Login process; done with writing login packet and should clear information - _physicalStateObj.HasPendingData = true; - _physicalStateObj._messageStatus = 0; - }// tdsLogin } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index c6dae1a007..a07c861a75 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1232,6 +1232,165 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( return PreLoginHandshakeStatus.Successful; } + internal void TdsLogin( + SqlLogin rec, + TdsEnums.FeatureExtension requestedFeatures, + SessionData recoverySessionData, + FederatedAuthenticationFeatureExtensionData fedAuthFeatureExtensionData, + SqlConnectionEncryptOption encrypt) + { + _physicalStateObj.SetTimeoutSeconds(rec.timeout); + + Debug.Assert(recoverySessionData == null || (requestedFeatures & TdsEnums.FeatureExtension.SessionRecovery) != 0, "Recovery session data without session recovery feature request"); + Debug.Assert(TdsEnums.MAXLEN_HOSTNAME >= rec.hostName.Length, "_workstationId.Length exceeds the max length for this value"); + + Debug.Assert(!(rec.useSSPI && _connHandler._fedAuthRequired), "Cannot use SSPI when server has responded 0x01 for FedAuthRequired PreLogin Option."); + Debug.Assert(!rec.useSSPI || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Cannot use both SSPI and FedAuth"); + Debug.Assert(fedAuthFeatureExtensionData == null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) != 0, "fedAuthFeatureExtensionData provided without fed auth feature request"); + Debug.Assert(fedAuthFeatureExtensionData != null || (requestedFeatures & TdsEnums.FeatureExtension.FedAuth) == 0, "Fed Auth feature requested without specifying fedAuthFeatureExtensionData."); + + Debug.Assert(rec.userName == null || (rec.userName != null && TdsEnums.MAXLEN_CLIENTID >= rec.userName.Length), "_userID.Length exceeds the max length for this value"); + Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTID >= rec.credential.UserId.Length), "_credential.UserId.Length exceeds the max length for this value"); + + Debug.Assert(rec.password == null || (rec.password != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.password.Length), "_password.Length exceeds the max length for this value"); + Debug.Assert(rec.credential == null || (rec.credential != null && TdsEnums.MAXLEN_CLIENTSECRET >= rec.credential.Password.Length), "_credential.Password.Length exceeds the max length for this value"); + + Debug.Assert(rec.credential != null || rec.userName != null || rec.password != null, "cannot mix the new secure password system and the connection string based password"); + Debug.Assert(rec.newSecurePassword != null || rec.newPassword != null, "cannot have both new secure change password and string based change password"); + Debug.Assert(TdsEnums.MAXLEN_APPNAME >= rec.applicationName.Length, "_applicationName.Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_SERVERNAME >= rec.serverName.Length, "_dataSource.Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_LANGUAGE >= rec.language.Length, "_currentLanguage .Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_DATABASE >= rec.database.Length, "_initialCatalog.Length exceeds the max length for this value"); + Debug.Assert(TdsEnums.MAXLEN_ATTACHDBFILE >= rec.attachDBFilename.Length, "_attachDBFileName.Length exceeds the max length for this value"); + + Debug.Assert(_connHandler != null, "SqlConnectionInternalTds handler can not be null at this point."); + _connHandler!.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); + _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth); + + // get the password up front to use in sspi logic below + byte[] encryptedPassword = null; + byte[] encryptedChangePassword = null; + int encryptedPasswordLengthInBytes; + int encryptedChangePasswordLengthInBytes; + bool useFeatureExt = (requestedFeatures != TdsEnums.FeatureExtension.None); + + string userName; + + if (rec.credential != null) + { + userName = rec.credential.UserId; + encryptedPasswordLengthInBytes = rec.credential.Password.Length * 2; + } + else + { + userName = rec.userName; + encryptedPassword = TdsParserStaticMethods.ObfuscatePassword(rec.password); + encryptedPasswordLengthInBytes = encryptedPassword.Length; // password in clear text is already encrypted and its length is in byte + } + + if (rec.newSecurePassword != null) + { + encryptedChangePasswordLengthInBytes = rec.newSecurePassword.Length * 2; + } + else + { + encryptedChangePassword = TdsParserStaticMethods.ObfuscatePassword(rec.newPassword); + encryptedChangePasswordLengthInBytes = encryptedChangePassword.Length; + } + + // set the message type + _physicalStateObj._outputMessageType = TdsEnums.MT_LOGIN7; + + // length in bytes + int length = TdsEnums.SQL2005_LOG_REC_FIXED_LEN; + + string clientInterfaceName = TdsEnums.SQL_PROVIDER_NAME; + Debug.Assert(TdsEnums.MAXLEN_CLIENTINTERFACE >= clientInterfaceName.Length, "cchCltIntName can specify at most 128 unicode characters. See Tds spec"); + + // add up variable-len portions (multiply by 2 for byte len of char strings) + // + checked + { + length += (rec.hostName.Length + rec.applicationName.Length + + rec.serverName.Length + clientInterfaceName.Length + + rec.language.Length + rec.database.Length + + rec.attachDBFilename.Length) * 2; + if (useFeatureExt) + { + length += 4; + } + } + + // allocate memory for SSPI variables + ArrayBufferWriter sspiWriter = null; + + try + { + // only add lengths of password and username if not using SSPI or requesting federated authentication info + if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) + { + checked + { + length += (userName.Length * 2) + encryptedPasswordLengthInBytes + + encryptedChangePasswordLengthInBytes; + } + } + else + { + if (rec.useSSPI) + { + sspiWriter = ObjectPools.BufferWriter.Rent(); + + // Call helper function for SSPI data and actual length. + // Since we don't have SSPI data from the server, send null for the + // byte[] buffer and 0 for the int length. + Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'"); + _physicalStateObj.SniContext = SniContext.Snix_LoginSspi; + _authenticationProvider.WriteSSPIContext(ReadOnlySpan.Empty, sspiWriter); + + _physicalStateObj.SniContext = SniContext.Snix_Login; + + checked + { + length += (int)sspiWriter.WrittenCount; + } + } + } + + int feOffset = length; + // calculate and reserve the required bytes for the featureEx + length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); + + WriteLoginData(rec, + requestedFeatures, + recoverySessionData, + fedAuthFeatureExtensionData, + encrypt, + encryptedPassword, + encryptedChangePassword, + encryptedPasswordLengthInBytes, + encryptedChangePasswordLengthInBytes, + useFeatureExt, + userName, + length, + feOffset, + clientInterfaceName, + sspiWriter is { } ? sspiWriter.WrittenSpan : ReadOnlySpan.Empty); + } + finally + { + if (sspiWriter is not null) + { + ObjectPools.BufferWriter.Return(sspiWriter); + } + } + + _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); + _physicalStateObj.ResetSecurePasswordsInformation(); // Password information is needed only from Login process; done with writing login packet and should clear information + _physicalStateObj.HasPendingData = true; + _physicalStateObj._messageStatus = 0; + } + internal void Deactivate(bool connectionIsDoomed) { // Called when the connection that owns us is deactivated. From c8be76e94abf6c7517f04b241506ba695af984ad Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:37:39 +0100 Subject: [PATCH 23/25] TdsParser: Move ProcessSSPI --- .../Data/SqlClient/TdsParser.SSPI.cs | 50 ------------------ .../src/Microsoft/Data/SqlClient/TdsParser.cs | 52 +++++++++++++++++++ 2 files changed, 52 insertions(+), 50 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs index 2775f4a2f1..88f866f54a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs @@ -11,56 +11,6 @@ namespace Microsoft.Data.SqlClient internal partial class TdsParser { - internal void ProcessSSPI(int receivedLength) - { - Debug.Assert(_authenticationProvider is not null); - - SniContext outerContext = _physicalStateObj.SniContext; - _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi; - - // allocate received buffer based on length from SSPI message - byte[] receivedBuff = ArrayPool.Shared.Rent(receivedLength); - - try - { - // read SSPI data received from server - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - // allocate send buffer and initialize length - var writer = ObjectPools.BufferWriter.Rent(); - - try - { - // make call for SSPI data - _authenticationProvider!.WriteSSPIContext(receivedBuff.AsSpan(0, receivedLength), writer); - - // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA! - _physicalStateObj.WriteByteSpan(writer.WrittenSpan); - - } - finally - { - ObjectPools.BufferWriter.Return(writer); - } - } - finally - { - ArrayPool.Shared.Return(receivedBuff, clearArray: true); - } - - // set message type so server knows its a SSPI response - _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI; - - // send to server - _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); - _physicalStateObj.SniContext = outerContext; - } - #nullable disable } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index a07c861a75..0aa7e28fb1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1391,6 +1391,58 @@ internal void TdsLogin( _physicalStateObj._messageStatus = 0; } +#nullable enable + internal void ProcessSSPI(int receivedLength) + { + Debug.Assert(_authenticationProvider is not null); + + SniContext outerContext = _physicalStateObj.SniContext; + _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi; + + // allocate received buffer based on length from SSPI message + byte[] receivedBuff = ArrayPool.Shared.Rent(receivedLength); + + try + { + // read SSPI data received from server + Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); + TdsOperationStatus result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength); + if (result != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } + + // allocate send buffer and initialize length + var writer = ObjectPools.BufferWriter.Rent(); + + try + { + // make call for SSPI data + _authenticationProvider!.WriteSSPIContext(receivedBuff.AsSpan(0, receivedLength), writer); + + // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA! + _physicalStateObj.WriteByteSpan(writer.WrittenSpan); + + } + finally + { + ObjectPools.BufferWriter.Return(writer); + } + } + finally + { + ArrayPool.Shared.Return(receivedBuff, clearArray: true); + } + + // set message type so server knows its a SSPI response + _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI; + + // send to server + _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); + _physicalStateObj.SniContext = outerContext; + } +#nullable disable + internal void Deactivate(bool connectionIsDoomed) { // Called when the connection that owns us is deactivated. From a2b56f67bffad5a1be5c3f4d563b64bf46c60c38 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:38:42 +0100 Subject: [PATCH 24/25] Remove now-unused TdsParser.SSPI.cs --- .../netcore/src/Microsoft.Data.SqlClient.csproj | 3 --- .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 --- .../Microsoft/Data/SqlClient/TdsParser.SSPI.cs | 16 ---------------- 3 files changed, 22 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 4853409443..d96e7aa247 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -771,9 +771,6 @@ Microsoft\Data\SqlClient\TdsParser.cs - - Microsoft\Data\SqlClient\TdsParser.SSPI.cs - Microsoft\Data\SqlClient\TdsParserHelperClasses.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index aa2e92fe52..2f917a158d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -444,9 +444,6 @@ Microsoft\Data\SqlClient\TdsParser.cs - - Microsoft\Data\SqlClient\TdsParser.SSPI.cs - Microsoft\Data\Sql\SqlDataSourceEnumerator.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs deleted file mode 100644 index 88f866f54a..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.SSPI.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Buffers; -using System.Diagnostics; -using System.Text; -using Microsoft.Data.SqlClient.Utilities; - -#nullable enable - -namespace Microsoft.Data.SqlClient -{ - - internal partial class TdsParser - { -#nullable disable - } -} From b9b10bacb4675ce9a39eacc21d7b3f2a3186d7d0 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:43:59 +0100 Subject: [PATCH 25/25] Remove now-unused stub files --- .../Data/SqlClient/SqlConnection.stub.cs | 96 --------------- .../Data/SqlClient/SqlDataReaderSmi.stub.cs | 17 --- .../SqlInternalConnectionSmi.stub.cs | 22 ---- .../Data/SqlClient/TdsParser.stub.cs | 109 ------------------ 4 files changed, 244 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.stub.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs deleted file mode 100644 index d888c2e637..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// @TODO: This is only a stub class for clearing errors while merging other files. - -using System; -using System.Data.Common; -using System.Threading.Tasks; -using Microsoft.Data.Common.ConnectionString; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.ConnectionPool; -using Microsoft.SqlServer.Server; - -namespace Microsoft.Data.SqlClient -{ - public class SqlConnection : DbConnection - { - internal bool _applyTransientFaultHandling = false; - internal Task _currentReconnectionTask = null; - internal SessionData _recoverySessionData = null; - - #region Constructors - - internal SqlConnection() {} - - internal SqlConnection(string connectionString) {} - - #endregion - - #region Properties - - internal Guid ClientConnectionId { get; set; } - - #if NETFRAMEWORK - internal static System.Security.CodeAccessPermission ExecutePermission { get; set; } - #endif - - internal bool HasLocalTransaction { get; set; } - - internal DbConnectionInternal InnerConnection { get; set; } - - internal bool Is2008OrNewer { get; set; } - - internal int ObjectID { get; set; } - - internal TdsParser Parser { get; set; } - - internal DbConnectionPoolGroup PoolGroup { get; set; } - - internal SqlStatistics Statistics { get; set; } - - internal bool StatisticsEnabled { get; set; } - - internal DbConnectionOptions UserConnectionOptions { get; set; } - - #endregion - - #region Methods - - internal void Abort(Exception e) { } - - internal void AddWeakReference(object value, int tag) { } - - internal SqlTransaction BeginTransaction() => - null; - - internal byte[] GetBytes(object o, out Format format, out int maxSize) - { - format = Format.Unknown; - maxSize = 0; - return null; - } - - internal SqlInternalConnectionTds GetOpenTdsConnection() => null; - - internal void PermissionDemand() { } - - internal Task RegisterForConnectionCloseNotification(Task outerTask, object value, int tag) => - Task.FromResult(default); - - internal void SetInnerConnectionEvent(DbConnectionInternal to) { } - - internal bool SetInnerConnectionFrom(DbConnectionInternal to, DbConnectionInternal from) => - false; - - internal void SetInnerConnectionTo(DbConnectionInternal to) { } - - internal void ValidateConnectionForExecute(string method, SqlCommand command) { } - - internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) => - null; - - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs deleted file mode 100644 index 005e7eb633..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// @TODO: This is only a stub class for clearing errors while merging other files. - -using System.Data; - -namespace Microsoft.Data.SqlClient -{ - internal class SqlDataReaderSmi : SqlDataReader - { - internal SqlDataReaderSmi() : base(null, CommandBehavior.Default) - { - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.stub.cs deleted file mode 100644 index 333cc3bf5f..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.stub.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; - -#if NETFRAMEWORK - -namespace Microsoft.Data.SqlClient -{ - // DO NOT USE THIS FILE IN ANY PROJECT! - // This is a temporary stub to enable migrating DbConnectionInternal to the common project. - internal abstract class SqlInternalConnectionSmi : SqlInternalConnection - { - protected SqlInternalConnectionSmi(SqlConnectionString connectionOptions) : base(connectionOptions) - { - throw new NotImplementedException(); - } - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs deleted file mode 100644 index 27bd25a989..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// @TODO: This is only a stub partial for clearing errors while merging other files. - -using System; -using System.Data.SqlTypes; -using System.Threading.Tasks; -using Microsoft.Data.Sql; - -namespace Microsoft.Data.SqlClient -{ - internal partial class TdsParser - { - internal bool _asyncWrite = false; - - internal SqlInternalConnectionTds Connection { get; set; } - - internal SqlInternalTransaction CurrentTransaction { get; set; } - - internal TdsParserState State { get; set; } - - internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal sqlValue, int scale) => - SqlDecimal.Null; - - internal object EncryptColumnValue( - object value, - SqlMetaDataPriv metadata, - string column, - TdsParserStateObject stateObj, - bool isDataFeed, - bool isSqlType) => - null; - - internal TdsParserStateObject GetSession(object owner) => - null; - - internal void LoadColumnEncryptionKeys( - _SqlMetaDataSet metadataCollection, - SqlConnection connection, - SqlCommand command = null) - { - } - - internal void Run( - RunBehavior runBehavior, - SqlCommand cmdHandler, - SqlDataReader dataStream, - BulkCopySimpleResultSet bulkCopyHandler, - TdsParserStateObject stateObj) - { - } - - #if NETFRAMEWORK - internal void RunReliably( - RunBehavior runBehavior, - SqlCommand cmdHandler, - SqlDataReader dataStream, - BulkCopySimpleResultSet bulkCopyHandler, - TdsParserStateObject stateObj) - { - } - #endif - - internal Task TdsExecuteSQLBatch( - string text, - int timeout, - SqlNotificationRequest notificationRequest, - TdsParserStateObject stateObj, - bool sync, - bool callerHasConnectionLock = false, - byte[] enclavePackage = null) => - null; - - internal bool ShouldEncryptValuesForBulkCopy() => - false; - - internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) => - Task.CompletedTask; - - internal void WriteBulkCopyMetaData( - _SqlMetaDataSet metadataCollection, - int count, - TdsParserStateObject stateObj) - { - } - - internal Task WriteBulkCopyValue( - object value, - SqlMetaDataPriv metadata, - TdsParserStateObject stateObj, - bool isSqlType, - bool isDataFeed, - bool isNull) => - Task.CompletedTask; - - internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObje) => - Task.CompletedTask; - - internal void WriteSqlVariantDate(DateTime value, TdsParserStateObject stateObj) - { - } - - internal void WriteSqlVariantDateTime2(DateTime value, TdsParserStateObject stateObj) - { - } - } -}