From 31b5f2866bb1df15fca2d6e280b0e461fde7b63f Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 31 Jul 2025 18:53:46 +0100 Subject: [PATCH 01/16] remove spare packet --- .../Data/SqlClient/TdsParserStateObject.cs | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) 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 343eaeca5a..dd93cc9731 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3796,21 +3796,6 @@ internal int GetPacketDataSize() return Math.Max(RunningDataSize - previous, 0); } - internal void Clear() - { - Buffer = null; - Read = 0; - NextPacket = null; - if (PrevPacket != null) - { - PrevPacket.NextPacket = null; - PrevPacket = null; - } - SetDebugStackImpl(null); - SetDebugPacketId(0); - SetDebugDataHash(); - } - internal void SetDebugStack(string value) => SetDebugStackImpl(value); internal void SetDebugPacketId(int value) => SetDebugPacketIdImpl(value); internal void SetDebugDataHash() => SetDebugDataHashImpl(); @@ -4107,7 +4092,6 @@ internal void Restore(TdsParserStateObject stateObj) private PacketData _firstPacket; private PacketData _current; private PacketData _continuePacket; - private PacketData _sparePacket; #if DEBUG private int _packetCounter; @@ -4188,15 +4172,8 @@ internal void AppendPacketData(byte[] buffer, int read) } } #endif - PacketData packetData = _sparePacket; - if (packetData is null) - { - packetData = new PacketData(); - } - else - { - _sparePacket = null; - } + PacketData packetData = new PacketData(); + packetData.Buffer = buffer; packetData.Read = read; #if DEBUG @@ -4380,13 +4357,10 @@ internal void Clear() private void ClearPackets() { - PacketData packet = _firstPacket; _firstPacket = null; _lastPacket = null; _continuePacket = null; _current = null; - packet.Clear(); - _sparePacket = packet; } private void ClearState() From 4b891d8ab914a8c2be889d7916e4c7b00597b7c1 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 31 Jul 2025 21:31:37 +0100 Subject: [PATCH 02/16] fix read error with reused uncleared packet fix 0 length read at the start of a packe in plp stream returning 0 when continuing handle char array sizing better change existing test to use multiple packet sizes --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 8 ++++---- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 8 ++++---- .../Data/SqlClient/TdsParserStateObject.cs | 16 ++++++++++------ .../SQL/DataReaderTest/DataReaderTest.cs | 14 ++++++++++---- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index cf53e8ecff..ce7feec086 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -13149,7 +13149,7 @@ bool writeDataSizeToSnapshot if (stateObj._longlen == 0) { Debug.Assert(stateObj._longlenleft == 0); - totalCharsRead = 0; + totalCharsRead = startOffsetByteCount >> 1; return TdsOperationStatus.Done; // No data } @@ -13169,14 +13169,14 @@ bool writeDataSizeToSnapshot // later needing to repeatedly allocate new target buffers and copy data as we discover new data if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && stateObj._longlen < (int.MaxValue >> 1)) { - if (supportRentedBuff && stateObj._longlen < 1073741824) // 1 Gib + if (supportRentedBuff && stateObj._longlen >> 1 < 1073741824) // 1 Gib { - buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen, len)); + buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen >> 1, len)); rentedBuff = true; } else { - buff = new char[(int)Math.Min((int)stateObj._longlen, len)]; + buff = new char[(int)Math.Min((int)stateObj._longlen >> 1, len)]; rentedBuff = false; } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index d65b0db83e..36895bf38d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -13312,7 +13312,7 @@ bool writeDataSizeToSnapshot if (stateObj._longlen == 0) { Debug.Assert(stateObj._longlenleft == 0); - totalCharsRead = 0; + totalCharsRead = startOffsetByteCount >> 1; return TdsOperationStatus.Done; // No data } @@ -13332,14 +13332,14 @@ bool writeDataSizeToSnapshot // allocate the whole buffer in one shot instead of realloc'ing and copying over each time if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && stateObj._longlen < (int.MaxValue >> 1)) { - if (supportRentedBuff && stateObj._longlen < 1073741824) // 1 Gib + if (supportRentedBuff && stateObj._longlen >> 1 < 1073741824) // 1 Gib { - buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen, len)); + buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen >> 1, len)); rentedBuff = true; } else { - buff = new char[(int)Math.Min((int)stateObj._longlen, len)]; + buff = new char[(int)Math.Min((int)stateObj._longlen >> 1, len)]; rentedBuff = 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 dd93cc9731..42a49ff5a5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3764,8 +3764,9 @@ internal sealed partial class StateSnapshot { private sealed partial class PacketData { - public byte[] Buffer; - public int Read; + public readonly byte[] Buffer; + public readonly int Read; + public PacketData NextPacket; public PacketData PrevPacket; @@ -3775,6 +3776,12 @@ private sealed partial class PacketData /// public int RunningDataSize; + public PacketData(byte[] buffer, int read) + { + Buffer = buffer; + Read = read; + } + public int PacketID => Packet.GetIDFromHeader(Buffer.AsSpan(0, TdsEnums.HEADER_LEN)); internal int GetPacketDataOffset() @@ -4172,10 +4179,7 @@ internal void AppendPacketData(byte[] buffer, int read) } } #endif - PacketData packetData = new PacketData(); - - packetData.Buffer = buffer; - packetData.Read = read; + PacketData packetData = new PacketData(buffer, read); #if DEBUG packetData.SetDebugStack(_stateObj._lastStack); packetData.SetDebugPacketId(Interlocked.Increment(ref _packetCounter)); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs index e9786bea16..ae5fa33ac3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -1035,11 +1035,18 @@ [Value] [nvarchar](max) NULL } } - int counter = 0; - while (counter < 10) + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); + builder.PersistSecurityInfo = true; + builder.Pooling = false; + + for (int packetSize = 512; packetSize<2048; packetSize++) { - using (var cmd = cn.CreateCommand()) + builder.PacketSize = packetSize; + using (SqlConnection sizedConnection = new SqlConnection(builder.ToString())) + using (SqlCommand cmd = sizedConnection.CreateCommand()) { + sizedConnection.Open(); + cmd.CommandText = $"SELECT [d].[Id], [d].[DocumentIdentificationId], [d].[Name], [d].[Value] FROM [{tableName}] AS [d]"; using (var reader = await cmd.ExecuteReaderAsync()) { @@ -1063,7 +1070,6 @@ [Value] [nvarchar](max) NULL } } } - counter++; } } finally From 924342f615a6700ee9a0afafae62a72ad924d248 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 1 Aug 2025 02:27:38 +0100 Subject: [PATCH 03/16] additinal fix for 0 length case --- .../Data/SqlClient/TdsParserStateObject.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) 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 42a49ff5a5..25862c0739 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -1998,7 +1998,8 @@ internal TdsOperationStatus TryReadStringWithEncoding(int length, System.Text.En if (isPlp) { - TdsOperationStatus result = TryReadPlpBytes(ref buf, 0, int.MaxValue, out length); + bool compatibilityMode = LocalAppContextSwitches.UseCompatibilityAsyncBehaviour; + TdsOperationStatus result = TryReadPlpBytes(ref buf, 0, int.MaxValue, out length, canContinue && !compatibilityMode, canContinue && !compatibilityMode, compatibilityMode); if (result != TdsOperationStatus.Done) { value = null; @@ -2160,12 +2161,10 @@ internal int ReadPlpBytesChunk(byte[] buff, int offset, int len) internal TdsOperationStatus TryReadPlpBytes(ref byte[] buff, int offset, int len, out int totalBytesRead) { bool canContinue = false; - bool isStarting = false; - bool isContinuing = false; bool compatibilityMode = LocalAppContextSwitches.UseCompatibilityAsyncBehaviour; if (!compatibilityMode) { - (canContinue, isStarting, isContinuing) = GetSnapshotStatuses(); + (canContinue, _, _) = GetSnapshotStatuses(); } return TryReadPlpBytes(ref buff, offset, len, out totalBytesRead, canContinue, canContinue, compatibilityMode); } @@ -2189,7 +2188,16 @@ internal TdsOperationStatus TryReadPlpBytes(ref byte[] buff, int offset, int len } AssertValidState(); - totalBytesRead = 0; + if (writeDataSizeToSnapshot && canContinue && _snapshot != null) + { + // if there is a snapshot which it contains a stored plp buffer take it + // and try to use it if it is the right length + buff = TryTakeSnapshotStorage() as byte[]; + if (buff != null) + { + totalBytesRead = _snapshot.GetPacketDataOffset(); + } + } return TdsOperationStatus.Done; // No data } From 6109626e0450e98c84afd32c94a4eff5b846dad2 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 28 Aug 2025 18:43:54 +0100 Subject: [PATCH 04/16] fix pending read counters force process sni compatibility mode by default --- .../Data/SqlClient/TdsParserStateObject.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) 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 25862c0739..80f518c1c0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3224,22 +3224,29 @@ internal void ReadSni(TaskCompletionSource completion) readPacket = ReadAsync(handle, out error); - if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) - { - DecrementPendingCallbacks(false); // Failure - we won't receive callback! + if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) + { + DecrementPendingCallbacks(false); // Failure - we won't receive callback! + } } } + else + { + // this call to IncrementPendingCallbacks is required for balance + // the _pendingCallbacks counter will be unconditionally decremented in ReadAsyncCallback + // so we must make sure that even though we are not making a network call that we do + // not cause an incorrect decrement which will cause disconnection from the native + // component + IncrementPendingCallbacks(); + readPacket = default; + error = TdsEnums.SNI_SUCCESS; + } } - else + finally { - readPacket = default; - error = TdsEnums.SNI_SUCCESS; + Interlocked.Decrement(ref _readingCount); } } - finally - { - Interlocked.Decrement(ref _readingCount); - } if (handle.IsNull) { From 23741295f85d0e3b60ffa59a8c515c79a814e593 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 13 Aug 2025 00:25:10 +0100 Subject: [PATCH 05/16] use division not shift --- .../netcore/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index ce7feec086..1f9113422c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -13149,7 +13149,7 @@ bool writeDataSizeToSnapshot if (stateObj._longlen == 0) { Debug.Assert(stateObj._longlenleft == 0); - totalCharsRead = startOffsetByteCount >> 1; + totalCharsRead = startOffsetByteCount / 2; return TdsOperationStatus.Done; // No data } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 36895bf38d..904959ac60 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -13312,7 +13312,7 @@ bool writeDataSizeToSnapshot if (stateObj._longlen == 0) { Debug.Assert(stateObj._longlenleft == 0); - totalCharsRead = startOffsetByteCount >> 1; + totalCharsRead = startOffsetByteCount / 2; return TdsOperationStatus.Done; // No data } From f88a354704e70db2cf45ec4acb46099836f6cf1b Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 15 Aug 2025 01:50:42 +0100 Subject: [PATCH 06/16] Fix cached buffer attempting to use storage when continue is not occuring --- .../src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs index 82ef37fb6b..a03eefce09 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCachedBuffer.cs @@ -39,10 +39,10 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser { buffer = null; - (bool canContinue, bool isStarting, _) = stateObj.GetSnapshotStatuses(); + (bool canContinue, bool isStarting, bool isContinuing) = stateObj.GetSnapshotStatuses(); List cachedBytes = null; - if (canContinue) + if (canContinue && isContinuing) { cachedBytes = stateObj.TryTakeSnapshotStorage() as List; if (isStarting) @@ -81,7 +81,7 @@ internal static TdsOperationStatus TryCreate(SqlMetaDataPriv metadata, TdsParser result = stateObj.TryReadPlpBytes(ref byteArr, 0, cb, out cb, canContinue, writeDataSizeToSnapshot: false, compatibilityMode: false); if (result != TdsOperationStatus.Done) { - if (result == TdsOperationStatus.NeedMoreData && canContinue && cb == byteArr.Length) + if (result == TdsOperationStatus.NeedMoreData && canContinue && cb == byteArr.Length && (isContinuing || !isStarting)) { // succeeded in getting the data but failed to find the next plp length returnAfterAdd = true; From 09dd588afe8958b22abfd0c5c4cc8c93d9bb31fd Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 15 Aug 2025 02:12:10 +0100 Subject: [PATCH 07/16] fix and clarify RequiredLength vs CurrentLength in ProcessSniPacket --- .../Data/SqlClient/TdsParserStateObject.Multiplexer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs index 77bd1c982b..8364666572 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs @@ -95,16 +95,18 @@ out recurse // if a partial packet was reconstructed it must be handled first if (consumePartialPacket) { + // the partial packet has been processed by the multiplexer and should now have + // only data from a single packet in it so we should use RequiredLength which + // is defined by the packet header here not CurrentLength if (_snapshot != null) { - _snapshot.AppendPacketData(PartialPacket.Buffer, PartialPacket.CurrentLength); + _snapshot.AppendPacketData(PartialPacket.Buffer, PartialPacket.RequiredLength); SetBuffer(new byte[_inBuff.Length], 0, 0); appended = true; } else { - SetBuffer(PartialPacket.Buffer, 0, PartialPacket.CurrentLength); - + SetBuffer(PartialPacket.Buffer, 0, PartialPacket.RequiredLength); } bufferIsPartialCompleted = true; ClearPartialPacket(); From bc0e48775b14f00bae6f3e2f52da979e484ec5f1 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 15 Aug 2025 02:39:26 +0100 Subject: [PATCH 08/16] sync assertions --- .../src/Microsoft/Data/SqlClient/TdsParserStateObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 80f518c1c0..97ae140390 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -4178,8 +4178,8 @@ internal void CloneCleanupAltMetaDataSetArray() internal void AppendPacketData(byte[] buffer, int read) { Debug.Assert(buffer != null, "packet data cannot be null"); - Debug.Assert(read >= TdsEnums.HEADER_LEN, "minimum packet length is TdsEnums.HEADER_LEN"); - Debug.Assert(TdsEnums.HEADER_LEN + Packet.GetDataLengthFromHeader(buffer) == read, "partially read packets cannot be appended to the snapshot"); + Debug.Assert(LocalAppContextSwitches.UseCompatibilityProcessSni || read >= TdsEnums.HEADER_LEN, "minimum packet length is TdsEnums.HEADER_LEN"); + Debug.Assert(LocalAppContextSwitches.UseCompatibilityProcessSni || TdsEnums.HEADER_LEN + Packet.GetDataLengthFromHeader(buffer) == read, "partially read packets cannot be appended to the snapshot"); #if DEBUG for (PacketData current = _firstPacket; current != null; current = current.NextPacket) { From e54144b61edb060761301baa2be7eba1f02f3194 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Fri, 15 Aug 2025 02:46:19 +0100 Subject: [PATCH 09/16] prevent multiple appends to an open snapshot --- .../Data/SqlClient/TdsParserStateObject.Multiplexer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs index 8364666572..719e078fb7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs @@ -151,6 +151,15 @@ out recurse if (remainderPacketProduced) { SetPartialPacket(remainderPacket); + if (appended && recurse) + { + // if we've appended to the snapshot already we can't recurse and append to it again because the + // snapshot might be cleared by the async cleanup functions + // the only way to get a recurse output from the multiplexer is if it has produced a remainder packet so + // assert that this is the case and the put the remainder packet in the partial packet so that it + // can be picked up in another call. + recurse = false; + } if (!bufferIsPartialCompleted) { // we are keeping the partial packet buffer so replace it with a new one From 0934b922136182ffd123e89ba7f63bdf187e2ea3 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Sat, 16 Aug 2025 22:03:21 +0100 Subject: [PATCH 10/16] add continue capability back and make it opt-in for supporting callee functions --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 ++ .../TdsParserStateObject.Multiplexer.cs | 19 ++++++---- .../Data/SqlClient/TdsParserStateObject.cs | 36 +++++++++++++++++-- .../TdsParserStateObject.TestHarness.cs | 8 +++++ 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 1f9113422c..0886e7c978 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -13055,6 +13055,8 @@ internal TdsOperationStatus TryReadPlpUnicodeCharsWithContinue(TdsParserStateObj char[] temp = null; bool buffIsRented = false; int startOffset = 0; + + stateObj.RequestContinue(true); (bool canContinue, bool isStarting, bool isContinuing) = stateObj.GetSnapshotStatuses(); if (canContinue) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs index 719e078fb7..9f0627f623 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.Multiplexer.cs @@ -41,6 +41,10 @@ public void ProcessSniPacket(PacketHandle packet, uint error) if (PartialPacketContainsCompletePacket()) { Packet partialPacket = _partialPacket; + // the partial packet can contain more than a single packet worth of data so to consume the + // partial packet we must use the CurrentLength not just the RequiredLength and then later + // the multiplexer will split out the complete packet for consumption and maintain the + // additional data SetBuffer(partialPacket.Buffer, 0, partialPacket.CurrentLength); ClearPartialPacket(); getDataError = TdsEnums.SNI_SUCCESS; @@ -50,7 +54,7 @@ public void ProcessSniPacket(PacketHandle packet, uint error) { if (_inBytesRead != 0) { - SetBuffer(new byte[_inBuff.Length], 0, 0); + NewBuffer(_inBuff.Length); } getDataError = GetSniPacket(packet, ref dataSize); } @@ -76,7 +80,7 @@ public void ProcessSniPacket(PacketHandle packet, uint error) { if (recurse && appended) { - SetBuffer(new byte[_inBuff.Length], 0, 0); + NewBuffer(_inBuff.Length); appended = false; } MultiplexPackets( @@ -98,10 +102,11 @@ out recurse // the partial packet has been processed by the multiplexer and should now have // only data from a single packet in it so we should use RequiredLength which // is defined by the packet header here not CurrentLength + Debug.Assert(PartialPacket != null && PartialPacket.RequiredLength == PartialPacket.CurrentLength); if (_snapshot != null) { _snapshot.AppendPacketData(PartialPacket.Buffer, PartialPacket.RequiredLength); - SetBuffer(new byte[_inBuff.Length], 0, 0); + NewBuffer(_inBuff.Length); appended = true; } else @@ -127,7 +132,7 @@ out recurse // if we SetBuffer here to clear the packet buffer we will break the attention handling which relies // on the attention containing packet remaining in the active buffer even if we're appending to the // snapshot so we will have to use the appended variable to prevent the same buffer being added again - //// SetBuffer(new byte[_inBuff.Length], 0, 0); + //// NewBuffer(_inBuff.Length); appended = true; } else @@ -143,7 +148,7 @@ out recurse // we don't process it if (!bufferIsPartialCompleted) { - SetBuffer(_inBuff, 0, 0); + NewBuffer(_inBuff.Length); } } @@ -164,7 +169,7 @@ out recurse { // we are keeping the partial packet buffer so replace it with a new one // unless we have already set the buffer to the partial packet buffer - SetBuffer(new byte[_inBuff.Length], 0, 0); + NewBuffer(_inBuff.Length); } } @@ -312,7 +317,7 @@ out bool recurse remainderPacket = new Packet { Buffer = new byte[dataBuffer.Length], - CurrentLength = remainderLength, + CurrentLength = remainderLength }; remainderPacket.SetCreatedBy(1); 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 97ae140390..c8117d75fd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -1293,6 +1293,13 @@ internal void SetBuffer(byte[] buffer, int inBytesUsed, int inBytesRead/*, [Call _inBytesRead = inBytesRead; } + internal void NewBuffer(int size) + { + _inBuff = new byte[size]; + _inBytesUsed = 0; + _inBytesRead = 0; + } + // This ensure that there is data available to be read in the buffer and that the header has been processed // NOTE: This method (and all it calls) should be retryable without replaying a snapshot internal TdsOperationStatus TryPrepareBuffer() @@ -1404,7 +1411,7 @@ internal bool SetPacketSize(int size) // Allocate or re-allocate _inBuff. if (_inBuff == null) { - SetBuffer(new byte[size], 0, 0); + NewBuffer(size); } else if (size != _inBuff.Length) { @@ -1431,7 +1438,7 @@ internal bool SetPacketSize(int size) else { // buffer is empty - just create the new one that is double the size of the old one - SetBuffer(new byte[size], 0, 0); + NewBuffer(size); } } @@ -1994,6 +2001,7 @@ internal TdsOperationStatus TryReadStringWithEncoding(int length, System.Text.En } byte[] buf = null; int offset = 0; + RequestContinue(true); (bool canContinue, bool isStarting, bool isContinuing) = GetSnapshotStatuses(); if (isPlp) @@ -3645,6 +3653,9 @@ internal void AddSnapshotDataSize(int countOfBytesCopiedFromCurrentPacket) _snapshot.SetPacketDataSize(countOfBytesCopiedFromCurrentPacket); } + /// + /// clears the stored count of bytes to be copied from the all packets in the snapshot + /// internal void ClearSnapshotDataSize() { Debug.Assert(_snapshot != null, "_snapshot must exist to store packet data size"); @@ -3671,6 +3682,17 @@ internal int GetSnapshotPacketID() return _snapshot.GetPacketID(); } + /// + /// sets a value on the snapshot to allow the ContinueEnabled property to return true.
+ /// this function should be called only by functions that explicitly support the snapshot status + /// status + ///
+ internal void RequestContinue(bool value) + { + Debug.Assert(_snapshot != null); + _snapshot.RequestContinue(value); + } + /// /// Debug Only: Ensures that the TdsParserStateObject has no lingering state and can safely be re-used /// @@ -4109,6 +4131,7 @@ internal void Restore(TdsParserStateObject stateObj) private StateObjectData _continueStateData; internal object _storage; + internal bool _continueRequested; private PacketData _lastPacket; private PacketData _firstPacket; @@ -4157,7 +4180,7 @@ internal void CheckStack(string trace) } #endif - public bool ContinueEnabled => !LocalAppContextSwitches.UseCompatibilityAsyncBehaviour; + public bool ContinueEnabled => _continueRequested && !LocalAppContextSwitches.UseCompatibilityAsyncBehaviour; internal void CloneNullBitmapInfo() { @@ -4293,6 +4316,7 @@ internal void CaptureAsContinue(TdsParserStateObject stateObj) if (ContinueEnabled) { Debug.Assert(_stateObj == stateObj); + Debug.Assert(_stateObj._bTmpRead == 0); if (_current is not null) { _continueStateData ??= new StateObjectData(); @@ -4302,6 +4326,11 @@ internal void CaptureAsContinue(TdsParserStateObject stateObj) } } + internal void RequestContinue(bool value) + { + _continueRequested = value; + } + internal void SetPacketDataSize(int size) { PacketData target = _current; @@ -4385,6 +4414,7 @@ private void ClearPackets() private void ClearState() { _storage = null; + _continueRequested = false; _replayStateData.Clear(_stateObj); _continueStateData?.Clear(_stateObj, trackStack: false); #if DEBUG diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs index 89807b0132..6e8f64fcc8 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs @@ -142,6 +142,14 @@ void SetBuffer(byte[] buffer, int inBytesUsed, int inBytesRead) _inBytesRead = inBytesRead; } + [DebuggerStepThrough] + internal void NewBuffer(int size) + { + _inBuff = new byte[size]; + _inBytesUsed = 0; + _inBytesRead = 0; + } + // stubs private LastIOTimer _lastSuccessfulIOTimer = new LastIOTimer(); private Parser _parser = new Parser(); From a91ec4f66bc411d365e9be1d0de806cb49c8da78 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Sat, 16 Aug 2025 22:12:25 +0100 Subject: [PATCH 11/16] reduce memory usage when reading strings in continue mode --- .../Data/SqlClient/TdsParserStateObject.cs | 100 ++++++++++-------- 1 file changed, 55 insertions(+), 45 deletions(-) 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 c8117d75fd..a49fb2a326 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -2217,22 +2217,25 @@ internal TdsOperationStatus TryReadPlpBytes(ref byte[] buff, int offset, int len // If total length is known up front, allocate the whole buffer in one shot instead of realloc'ing and copying over each time if (buff == null && _longlen != TdsEnums.SQL_PLP_UNKNOWNLEN) { - if (compatibilityMode && _snapshot != null && _snapshotStatus != SnapshotStatus.NotActive) + if (_snapshot != null) { - // legacy replay path perf optimization - // if there is a snapshot which contains a stored plp buffer take it - // and try to use it if it is the right length - buff = TryTakeSnapshotStorage() as byte[]; - } - else if (writeDataSizeToSnapshot && canContinue && _snapshot != null) - { - // if there is a snapshot which it contains a stored plp buffer take it - // and try to use it if it is the right length - buff = TryTakeSnapshotStorage() as byte[]; - if (buff != null) + if (compatibilityMode && _snapshotStatus != SnapshotStatus.NotActive) + { + // legacy replay path perf optimization + // if there is a snapshot which contains a stored plp buffer take it + // and try to use it if it is the right length + buff = TryTakeSnapshotStorage() as byte[]; + } + else { - offset = _snapshot.GetPacketDataOffset(); - totalBytesRead = offset; + // if there is a snapshot which it contains a stored plp buffer take it + // and try to use it if it is the right length + buff = TryTakeSnapshotStorage() as byte[]; + if (buff != null && writeDataSizeToSnapshot && canContinue) + { + offset = _snapshot.GetPacketDataOffset(); + totalBytesRead = offset; + } } } @@ -2247,17 +2250,19 @@ internal TdsOperationStatus TryReadPlpBytes(ref byte[] buff, int offset, int len if (_longlenleft == 0) { TdsOperationStatus result = TryReadPlpLength(false, out _); - if (result != TdsOperationStatus.Done) + if (result != TdsOperationStatus.Done || _longlenleft == 0) { - totalBytesRead = 0; + if (_snapshot != null) + { + if (writeDataSizeToSnapshot && canContinue && buff != null) + { + totalBytesRead = _snapshot.GetPacketDataOffset(); + ClearSnapshotDataSize(); + } + SetSnapshotStorage(null); + } return result; } - if (_longlenleft == 0) - { - // Data read complete - totalBytesRead = 0; - return TdsOperationStatus.Done; - } } if (buff == null) @@ -2287,21 +2292,24 @@ internal TdsOperationStatus TryReadPlpBytes(ref byte[] buff, int offset, int len _longlenleft -= (ulong)bytesRead; if (result != TdsOperationStatus.Done) { - if (compatibilityMode && _snapshot != null) + if (_snapshot != null) { - // legacy replay path perf optimization - // a partial read has happened so store the target buffer in the snapshot - // so it can be re-used when another packet arrives and we read again - SetSnapshotStorage(buff); - } - else if (canContinue) - { - // a partial read has happened so store the target buffer in the snapshot - // so it can be re-used when another packet arrives and we read again - SetSnapshotStorage(buff); - if (writeDataSizeToSnapshot) + if (compatibilityMode) + { + // legacy replay path perf optimization + // a partial read has happened so store the target buffer in the snapshot + // so it can be re-used when another packet arrives and we read again + SetSnapshotStorage(buff); + } + else if (canContinue) { - AddSnapshotDataSize(bytesRead); + // a partial read has happened so store the target buffer in the snapshot + // so it can be re-used when another packet arrives and we read again + SetSnapshotStorage(buff); + if (writeDataSizeToSnapshot) + { + AddSnapshotDataSize(bytesRead); + } } } return result; @@ -2317,16 +2325,19 @@ internal TdsOperationStatus TryReadPlpBytes(ref byte[] buff, int offset, int len result = TryReadPlpLength(false, out _); if (result != TdsOperationStatus.Done) { - if (compatibilityMode && _snapshot != null) + if (_snapshot != null) { - // a partial read has happened so store the target buffer in the snapshot - // so it can be re-used when another packet arrives and we read again - SetSnapshotStorage(buff); - } - else if (canContinue && result == TdsOperationStatus.NeedMoreData) - { - SetSnapshotStorage(buff); - // data bytes read from the current packet must be 0 here so do not save the snapshot data size + if (compatibilityMode) + { + // a partial read has happened so store the target buffer in the snapshot + // so it can be re-used when another packet arrives and we read again + SetSnapshotStorage(buff); + } + else if (result == TdsOperationStatus.NeedMoreData) + { + SetSnapshotStorage(buff); + // data bytes read from the current packet must be 0 here so do not save the snapshot data size + } } return result; } @@ -2348,7 +2359,6 @@ internal TdsOperationStatus TryReadPlpBytes(ref byte[] buff, int offset, int len } return TdsOperationStatus.Done; } - ///////////////////////////////////////// // Value Skip Logic // ///////////////////////////////////////// From 8b6e985bde5e08adc79480a076e561b77b3963be Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Sat, 16 Aug 2025 22:20:06 +0100 Subject: [PATCH 12/16] avoid 0 length array allocations --- .../Microsoft/Data/SqlClient/TdsParserStaticMethods.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs index e21bfc7826..569a3fbc16 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -100,6 +100,10 @@ static internal void AliasRegistryLookup(ref string host, ref string protocol) // If this logic changed, SNIPacketSetData needs to be changed as well internal static byte[] ObfuscatePassword(string password) { + if (string.IsNullOrEmpty(password)) + { + return Array.Empty(); + } byte[] bObfuscated = new byte[password.Length << 1]; int s; byte bLo; @@ -118,6 +122,10 @@ internal static byte[] ObfuscatePassword(string password) internal static byte[] ObfuscatePassword(byte[] password) { + if (password == null || password.Length == 0) + { + return Array.Empty(); + } byte bLo; byte bHi; From 737ccf9b79597399554a1d3de1ae454795b90e05 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Sun, 17 Aug 2025 00:19:54 +0100 Subject: [PATCH 13/16] fix ReqeustContinue to be sync safe --- .../src/Microsoft/Data/SqlClient/TdsParserStateObject.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 a49fb2a326..6dbb1ddab5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3699,8 +3699,10 @@ internal int GetSnapshotPacketID() /// internal void RequestContinue(bool value) { - Debug.Assert(_snapshot != null); - _snapshot.RequestContinue(value); + if (_snapshot != null) + { + _snapshot.RequestContinue(value); + } } /// From 2e38fa9fadfa879f9c98a1f132b4752cf9ebfb5f Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Wed, 20 Aug 2025 20:23:37 +0100 Subject: [PATCH 14/16] fix 3527 shotren new test duration by using larger increments --- .../src/Microsoft/Data/SqlClient/SqlDataReader.cs | 8 +++++--- .../ManualTests/SQL/DataReaderTest/DataReaderTest.cs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 5e37fc1a47..22da8a07a3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -4238,9 +4238,7 @@ private TdsOperationStatus TryResetBlobState() else { Debug.Assert( - (_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1) - && - (_stateObj._longlen == 0 || _stateObj.IsSnapshotContinuing()), + (_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1), "Haven't read header yet, but column is partially read?" ); } @@ -5395,6 +5393,10 @@ private static Task GetFieldValueAsyncExecute(Task task, object state) { return Task.FromResult(reader.GetFieldValueFromSqlBufferInternal(reader._data[columnIndex], reader._metaData[columnIndex], isAsync: true)); } + else + { + return reader.ExecuteAsyncCall(context); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs index ae5fa33ac3..b071eeaf5a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -1039,7 +1039,7 @@ [Value] [nvarchar](max) NULL builder.PersistSecurityInfo = true; builder.Pooling = false; - for (int packetSize = 512; packetSize<2048; packetSize++) + for (int packetSize = 512; packetSize<2048; packetSize+=3) { builder.PacketSize = packetSize; using (SqlConnection sizedConnection = new SqlConnection(builder.ToString())) From 64f45c656c31d36e8a67fbd111a9d992a37ab211 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 28 Aug 2025 19:11:05 +0100 Subject: [PATCH 15/16] fix rebase merge error --- .../Data/SqlClient/TdsParserStateObject.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) 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 6dbb1ddab5..b72e38631f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -3242,29 +3242,28 @@ internal void ReadSni(TaskCompletionSource completion) readPacket = ReadAsync(handle, out error); - if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) - { - DecrementPendingCallbacks(false); // Failure - we won't receive callback! - } + if (!(TdsEnums.SNI_SUCCESS == error || TdsEnums.SNI_SUCCESS_IO_PENDING == error)) + { + DecrementPendingCallbacks(false); // Failure - we won't receive callback! } } - else - { - // this call to IncrementPendingCallbacks is required for balance - // the _pendingCallbacks counter will be unconditionally decremented in ReadAsyncCallback - // so we must make sure that even though we are not making a network call that we do - // not cause an incorrect decrement which will cause disconnection from the native - // component - IncrementPendingCallbacks(); - readPacket = default; - error = TdsEnums.SNI_SUCCESS; - } } - finally + else { - Interlocked.Decrement(ref _readingCount); + // this call to IncrementPendingCallbacks is required for balance + // the _pendingCallbacks counter will be unconditionally decremented in ReadAsyncCallback + // so we must make sure that even though we are not making a network call that we do + // not cause an incorrect decrement which will cause disconnection from the native + // component + IncrementPendingCallbacks(); + readPacket = default; + error = TdsEnums.SNI_SUCCESS; } } + finally + { + Interlocked.Decrement(ref _readingCount); + } if (handle.IsNull) { From 916db6a8adbd23eac7f493cb448d8231110cfbd6 Mon Sep 17 00:00:00 2001 From: Wraith2 Date: Thu, 28 Aug 2025 19:12:48 +0100 Subject: [PATCH 16/16] review feedback, add local for char size --- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 7 ++++--- .../netfx/src/Microsoft/Data/SqlClient/TdsParser.cs | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 0886e7c978..8fbeb3050c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -13171,14 +13171,15 @@ bool writeDataSizeToSnapshot // later needing to repeatedly allocate new target buffers and copy data as we discover new data if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && stateObj._longlen < (int.MaxValue >> 1)) { - if (supportRentedBuff && stateObj._longlen >> 1 < 1073741824) // 1 Gib + int stateLen = (int)stateObj._longlen >> 1; + if (supportRentedBuff && stateLen < 1073741824) // 1 Gib { - buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen >> 1, len)); + buff = ArrayPool.Shared.Rent(Math.Min(stateLen, len)); rentedBuff = true; } else { - buff = new char[(int)Math.Min((int)stateObj._longlen >> 1, len)]; + buff = new char[Math.Min(stateLen, len)]; rentedBuff = false; } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 904959ac60..d38190a359 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -13327,19 +13327,20 @@ bool writeDataSizeToSnapshot ); charsLeft = len; - // If total length is known up front, the length isn't specified as unknown - // and the caller doesn't pass int.max/2 indicating that it doesn't know the length - // allocate the whole buffer in one shot instead of realloc'ing and copying over each time + // If total data length is known up front from the plp header by being not SQL_PLP_UNKNOWNLEN + // and the number of chars required is less than int.max/2 allocate the entire buffer now to avoid + // later needing to repeatedly allocate new target buffers and copy data as we discover new data if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && stateObj._longlen < (int.MaxValue >> 1)) { - if (supportRentedBuff && stateObj._longlen >> 1 < 1073741824) // 1 Gib + int stateLen = (int)stateObj._longlen >> 1; + if (supportRentedBuff && stateLen < 1073741824) // 1 Gib { - buff = ArrayPool.Shared.Rent((int)Math.Min((int)stateObj._longlen >> 1, len)); + buff = ArrayPool.Shared.Rent(Math.Min(stateLen, len)); rentedBuff = true; } else { - buff = new char[(int)Math.Min((int)stateObj._longlen >> 1, len)]; + buff = new char[Math.Min(stateLen, len)]; rentedBuff = false; } }