Repository: dotnet/SqlClient
Pull request #3353: Merge | TdsParserStateObject PacketHandle usage
Author: login: edwardneal
Labels: name: Common Project 🚮
description: Things that relate to the common project project
Created: 2025-05-16T22:29:04Z | Merged: 2025-05-23T16:20:25Z

Relates to #1261.

This continues the `TdsParserStateObject` merge work. At present, there's a deviation between netcore and netfx: netcore uses a `PacketHandle` type to shuttle the native or managed packet handles around, while netfx just treats PacketHandle as an alias for `IntPtr`. This PR is focused strictly on merging that handling, so we can move on to the identical methods which use the type.


Diff:
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 eb556a79ec..eb0504f047 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -878,8 +878,8 @@
     <Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\LocalDb\LocalDbApi.Windows.cs">
       <Link>Microsoft\Data\SqlClient\LocalDb\LocalDbApi.Windows.cs</Link>
     </Compile>
-    <Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\PacketHandle.netcore.Windows.cs">
-      <Link>Microsoft\Data\SqlClient\PacketHandle.netcore.Windows.cs</Link>
+    <Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\PacketHandle.Windows.cs">
+      <Link>Microsoft\Data\SqlClient\PacketHandle.Windows.cs</Link>
     </Compile>
     <Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\SessionHandle.Windows.cs">
       <Link>Microsoft\Data\SqlClient\SessionHandle.Windows.cs</Link>
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 e1c3277fb7..23380b07a6 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -447,6 +447,9 @@
     <Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\Packet.cs">
       <Link>Microsoft\Data\SqlClient\Packet.cs</Link>
     </Compile>
+    <Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\PacketHandle.Windows.cs">
+      <Link>Microsoft\Data\SqlClient\PacketHandle.Windows.cs</Link>
+    </Compile>
     <Compile Include="$(CommonSourceRoot)Microsoft\Data\SqlClient\ParameterPeekAheadValue.cs">
       <Link>Microsoft\Data\SqlClient\ParameterPeekAheadValue.cs</Link>
     </Compile>
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 49f350f904..d061e759c9 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
@@ -18,8 +18,6 @@
 
 namespace Microsoft.Data.SqlClient
 {
-    using PacketHandle = IntPtr;
-
     internal partial class TdsParserStateObject
     {
         protected SNIHandle _sessionHandle = null;              // the SNI handle we're to work on
@@ -138,27 +136,35 @@ internal void CreatePhysicalSNIHandle(
                 ipPreference, cachedDNSInfo, hostNameInCertificate);
         }
 
-        internal bool IsPacketEmpty(PacketHandle readPacket) => readPacket == default;
+        internal bool IsPacketEmpty(PacketHandle readPacket)
+        {
+            Debug.Assert(readPacket.Type == PacketHandle.NativePointerType || readPacket.Type == 0, "unexpected packet type when requiring NativePointer");
+            return IntPtr.Zero == readPacket.NativePointer;
+        }
 
         internal PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error)
         {
             SNIHandle handle = Handle ?? throw ADP.ClosedConnectionError();
-            PacketHandle readPacket = default;
-            error = SniNativeWrapper.SniReadSyncOverAsync(handle, ref readPacket, timeoutRemaining);
-            return readPacket;
+            IntPtr readPacketPtr = IntPtr.Zero;
+            error = SniNativeWrapper.SniReadSyncOverAsync(handle, ref readPacketPtr, timeoutRemaining);
+            return PacketHandle.FromNativePointer(readPacketPtr);
         }
 
         internal PacketHandle ReadAsync(SessionHandle handle, out uint error)
         {
-            PacketHandle readPacket = default;
-            error = SniNativeWrapper.SniReadAsync(handle.NativeHandle, ref readPacket);
-            return readPacket;
+            IntPtr readPacketPtr = IntPtr.Zero;
+            error = SniNativeWrapper.SniReadAsync(handle.NativeHandle, ref readPacketPtr);
+            return PacketHandle.FromNativePointer(readPacketPtr);
         }
 
         internal uint CheckConnection() => SniNativeWrapper.SniCheckConnection(Handle);
 
-        internal void ReleasePacket(PacketHandle syncReadPacket) => SniNativeWrapper.SniPacketRelease(syncReadPacket);
-        
+        internal void ReleasePacket(PacketHandle syncReadPacket)
+        {
+            Debug.Assert(syncReadPacket.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer");
+            SniNativeWrapper.SniPacketRelease(syncReadPacket.NativePointer);
+        }
+
         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
         internal int DecrementPendingCallbacks(bool release)
         {
@@ -377,7 +383,13 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
 
         private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
         {
-            return SniNativeWrapper.SniPacketGetData(packet, _inBuff, ref dataSize);
+            return SniPacketGetData(packet, _inBuff, ref dataSize);
+        }
+
+        private uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize)
+        {
+            Debug.Assert(packet.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer");
+            return SniNativeWrapper.SniPacketGetData(packet.NativePointer, _inBuff, ref dataSize);
         }
 
         public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
@@ -411,7 +423,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
             bool processFinallyBlock = true;
             try
             {
-                Debug.Assert(IntPtr.Zero == packet || IntPtr.Zero != packet && source != null, "AsyncResult null on callback");
+                Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback");
 
                 if (_parser.MARSOn)
                 {
@@ -481,6 +493,13 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error)
             }
         }
 
+        private bool CheckPacket(PacketHandle packet, TaskCompletionSource<object> source)
+        {
+            Debug.Assert(packet.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer");
+            IntPtr ptr = packet.NativePointer;
+            return IntPtr.Zero == ptr || IntPtr.Zero != ptr && source != null;
+        }
+
 #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)
@@ -654,7 +673,7 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false)
 
 #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile
 
-        private Task SNIWritePacket(SNIPacket packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false)
+        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);
@@ -696,7 +715,8 @@ private Task SNIWritePacket(SNIPacket packet, out uint sniError, bool canAccumul
             }
             finally
             {
-                sniError = SniNativeWrapper.SniWritePacket(Handle, packet, sync);
+                Debug.Assert(packet.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePacket");
+                sniError = SniNativeWrapper.SniWritePacket(Handle, packet.NativePacket, sync);
             }
 
             if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING)
@@ -790,7 +810,15 @@ private Task SNIWritePacket(SNIPacket packet, out uint sniError, bool canAccumul
 
 #pragma warning restore 420
 
-        internal bool IsValidPacket(PacketHandle packetPointer) => packetPointer != default;
+        internal bool IsValidPacket(PacketHandle packetPointer)
+        {
+            Debug.Assert(packetPointer.Type == PacketHandle.NativePointerType || packetPointer.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePointer");
+            return (
+                (packetPointer.Type == PacketHandle.NativePointerType && packetPointer.NativePointer != IntPtr.Zero)
+                ||
+                (packetPointer.Type == PacketHandle.NativePacketType && packetPointer.NativePacket != null)
+            );
+        }
 
         // Sends an attention signal - executing thread will consume attn.
         internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false)
@@ -805,10 +833,7 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa
                     return;
                 }
 
-                SNIPacket attnPacket = new SNIPacket(Handle);
-                _sniAsyncAttnPacket = attnPacket;
-
-                SniNativeWrapper.SniPacketSetData(attnPacket, SQL.AttentionHeader, TdsEnums.HEADER_LEN, null, null);
+                PacketHandle attnPacket = CreateAndSetAttentionPacket();
 
                 RuntimeHelpers.PrepareConstrainedRegions();
                 try
@@ -868,11 +893,20 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa
             }
         }
 
+        internal PacketHandle CreateAndSetAttentionPacket()
+        {
+            SNIPacket attnPacket = new SNIPacket(Handle);
+            _sniAsyncAttnPacket = attnPacket;
+            SniNativeWrapper.SniPacketSetData(attnPacket, SQL.AttentionHeader, TdsEnums.HEADER_LEN, null, null);
+            return PacketHandle.FromNativePacket(attnPacket);
+        }
+
         private Task WriteSni(bool canAccumulate)
         {
             // Prepare packet, and write to packet.
-            SNIPacket packet = GetResetWritePacket();
-            SniNativeWrapper.SniPacketSetData(packet, _outBuff, _outBytesUsed, _securePasswords, _securePasswordOffsetsInBuffer);
+            PacketHandle packet = GetResetWritePacket();
+            SNIPacket nativePacket = packet.NativePacket;
+            SniNativeWrapper.SniPacketSetData(nativePacket, _outBuff, _outBytesUsed, _securePasswords, _securePasswordOffsetsInBuffer);
 
             Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock");
             Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true);
@@ -923,7 +957,7 @@ private Task WriteSni(bool canAccumulate)
             return task;
         }
 
-        internal SNIPacket GetResetWritePacket()
+        internal PacketHandle GetResetWritePacket()
         {
             if (_sniPacket != null)
             {
@@ -936,7 +970,7 @@ internal SNIPacket GetResetWritePacket()
                     _sniPacket = _writePacketCache.Take(Handle);
                 }
             }
-            return _sniPacket;
+            return PacketHandle.FromNativePacket(_sniPacket);
         }
 
         internal void ClearAllWritePackets()
@@ -953,8 +987,10 @@ internal void ClearAllWritePackets()
             }
         }
 
-        private IntPtr AddPacketToPendingList(SNIPacket packet)
+        private PacketHandle AddPacketToPendingList(PacketHandle packetToAdd)
         {
+            Debug.Assert(packetToAdd.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePacket");
+            SNIPacket packet = packetToAdd.NativePacket;
             Debug.Assert(packet == _sniPacket, "Adding a packet other than the current packet to the pending list");
             _sniPacket = null;
             IntPtr pointer = packet.DangerousGetHandle();
@@ -964,16 +1000,17 @@ private IntPtr AddPacketToPendingList(SNIPacket packet)
                 _pendingWritePackets.Add(pointer, packet);
             }
 
-            return pointer;
+            return PacketHandle.FromNativePointer(pointer);
         }
 
-        private void RemovePacketFromPendingList(IntPtr pointer)
+        private void RemovePacketFromPendingList(PacketHandle ptr)
         {
-            SNIPacket recoveredPacket;
+            Debug.Assert(ptr.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer");
+            IntPtr pointer = ptr.NativePointer;
 
             lock (_writePacketLockObject)
             {
-                if (_pendingWritePackets.TryGetValue(pointer, out recoveredPacket))
+                if (_pendingWritePackets.TryGetValue(pointer, out SNIPacket recoveredPacket))
                 {
                     _pendingWritePackets.Remove(pointer);
                     _writePacketCache.Add(recoveredPacket);
@@ -1033,6 +1070,6 @@ 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);
         }
 
-        protected PacketHandle EmptyReadPacket => default;
+        protected PacketHandle EmptyReadPacket => PacketHandle.FromNativePointer(default);
     }
 }
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs
similarity index 60%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Windows.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs
index aca8292100..9e8c3f6fc7 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs
@@ -2,8 +2,6 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-#if NET
-
 using System;
 
 namespace Microsoft.Data.SqlClient
@@ -20,15 +18,32 @@ namespace Microsoft.Data.SqlClient
 
     internal readonly ref struct PacketHandle
     {
+        /// <summary>
+        /// PacketHandle is transporting a native pointer. The NativePointer field is valid.
+        /// A PacketHandle has this type when managed code is referencing a pointer to a
+        /// packet which has been read from the native SNI layer.
+        /// </summary>
         public const int NativePointerType = 1;
+        /// <summary>
+        /// PacketHandle is transporting a native packet. The NativePacket field is valid.
+        /// A PacketHandle has this type when managed code is directly referencing a packet
+        /// which is due to be passed to the native SNI layer.
+        /// </summary>
         public const int NativePacketType = 2;
+#if NET
+        /// <summary>
+        /// PacketHandle is transporting a managed packet. The ManagedPacket field is valid.
+        /// A PacketHandle used by the managed SNI layer will always have this type.
+        /// </summary>
         public const int ManagedPacketType = 3;
 
         public readonly SNI.SNIPacket ManagedPacket;
+#endif
         public readonly SNIPacket NativePacket;
         public readonly IntPtr NativePointer;
         public readonly int Type;
 
+#if NET
         private PacketHandle(IntPtr nativePointer, SNIPacket nativePacket, SNI.SNIPacket managedPacket, int type)
         {
             Type = type;
@@ -36,7 +51,16 @@ private PacketHandle(IntPtr nativePointer, SNIPacket nativePacket, SNI.SNIPacket
             NativePointer = nativePointer;
             NativePacket = nativePacket;
         }
+#else
+        private PacketHandle(IntPtr nativePointer, SNIPacket nativePacket, int type)
+        {
+            Type = type;
+            NativePointer = nativePointer;
+            NativePacket = nativePacket;
+        }
+#endif
 
+#if NET
         public static PacketHandle FromManagedPacket(SNI.SNIPacket managedPacket) =>
             new PacketHandle(default, default, managedPacket, ManagedPacketType);
 
@@ -45,9 +69,12 @@ public static PacketHandle FromNativePointer(IntPtr nativePointer) =>
 
         public static PacketHandle FromNativePacket(SNIPacket nativePacket) =>
             new PacketHandle(default, nativePacket, default, NativePacketType);
+#else
+        public static PacketHandle FromNativePointer(IntPtr nativePointer) =>
+            new PacketHandle(nativePointer, default, NativePointerType);
 
-
+        public static PacketHandle FromNativePacket(SNIPacket nativePacket) =>
+            new PacketHandle(default, nativePacket, NativePacketType);
+#endif
     }
 }
-
-#endif
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Unix.cs
index 3ec188d79d..7a560a1896 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Unix.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Unix.cs
@@ -18,8 +18,22 @@ namespace Microsoft.Data.SqlClient
 
     internal readonly ref struct PacketHandle
     {
+        /// <summary>
+        /// PacketHandle is transporting a native pointer. The NativePointer field is valid.
+        /// A PacketHandle has this type when managed code is referencing a pointer to a
+        /// packet which has been read from the native SNI layer.
+        /// </summary>
         public const int NativePointerType = 1;
+        /// <summary>
+        /// PacketHandle is transporting a native packet. The NativePacket field is valid.
+        /// A PacketHandle has this type when managed code is directly referencing a packet
+        /// which is due to be passed to the native SNI layer.
+        /// </summary>
         public const int NativePacketType = 2;
+        /// <summary>
+        /// PacketHandle is transporting a managed packet. The ManagedPacket field is valid.
+        /// A PacketHandle used by the managed SNI layer will always have this type.
+        /// </summary>
         public const int ManagedPacketType = 3;
 
         public readonly SNI.SNIPacket ManagedPacket;
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs
index f2f9143ed7..37dc781dbc 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs
@@ -107,11 +107,7 @@ private static void ReadDispatcher(IntPtr key, IntPtr packet, uint error)
 
                 if (stateObj != null)
                 {
-#if NETFRAMEWORK
-                    stateObj.ReadAsyncCallback(IntPtr.Zero, packet, error);
-#else
                     stateObj.ReadAsyncCallback(IntPtr.Zero, PacketHandle.FromNativePointer(packet), error);
-#endif // NETFRAMEWORK
                 }
             }
         }
@@ -132,11 +128,7 @@ private static void WriteDispatcher(IntPtr key, IntPtr packet, uint error)
 
                 if (stateObj != null)
                 {
-#if NETFRAMEWORK
-                    stateObj.WriteAsyncCallback(IntPtr.Zero, packet, error);
-#else
                     stateObj.WriteAsyncCallback(IntPtr.Zero, PacketHandle.FromNativePointer(packet), error);
-#endif // NETFRAMEWORK
                 }
             }
         }
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 19d3d5add2..77bd1c982b 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
@@ -7,9 +7,6 @@
 
 namespace Microsoft.Data.SqlClient
 {
-#if NETFRAMEWORK
-    using PacketHandle = IntPtr;
-#endif
     partial class TdsParserStateObject
     {
         private Packet _partialPacket;
@@ -513,12 +510,7 @@ public void ProcessSniPacketCompat(PacketHandle packet, uint error)
             else
             {
                 uint dataSize = 0;
-                
-                #if NETFRAMEWORK
-                uint getDataError = SniNativeWrapper.SniPacketGetData(packet, _inBuff, ref dataSize);
-                #else
                 uint getDataError = SniPacketGetData(packet, _inBuff, ref dataSize);
-                #endif
 
                 if (getDataError == TdsEnums.SNI_SUCCESS)
                 {
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 8cbc275043..61cc18b536 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -19,7 +19,6 @@
 namespace Microsoft.Data.SqlClient
 {
 #if NETFRAMEWORK
-    using PacketHandle = IntPtr;
     using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers;
 #endif
     
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
index b746e7c0ad..e448b66b83 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs
@@ -10,13 +10,10 @@
 
 namespace Microsoft.Data.SqlClient
 {
-#if NETFRAMEWORK
-    using PacketHandle = IntPtr; 
-#elif NETCOREAPP
     internal struct PacketHandle
     {
     }
-#endif
+
     internal partial class TdsParserStateObject
     {
         internal int ObjectID = 1;


Reviews:
user: login: mdaigle
body: All of your changes look reasonable.

Would you be able to add a high level summary to the PacketHandle class docs that covers when the different packet types are used?  In particular, it's not obvious to me when we would use NativePointerType vs NativePacketType.

Bigger picture (and not blocking for this PR, just looking to discuss), do you have any insight into what this design pattern gives us? It feels like we're fighting the type system. I can't think of a reason why we'd want this pattern vs just using the underlying types directly or a ref struct per underlying type, but I may be missing something.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T16:15:39Z
user: login: benrr101
body: Would like to think through the PacketHandle class before we approve this. But otherwise, I think these will be good changes to move us through the merge classes.
Also, this is going to run into conflicts with #3345...
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T21:47:06Z
user: login: edwardneal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T22:24:21Z
user: login: edwardneal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T04:25:56Z
user: login: edwardneal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T04:30:46Z
user: login: edwardneal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T04:32:44Z
user: login: benrr101
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:05:49Z
user: login: benrr101
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:09:35Z
user: login: benrr101
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:09:55Z
user: login: benrr101
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:10:58Z
user: login: benrr101
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T17:11:40Z
user: login: mdaigle
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T20:35:20Z

Review comments:
diff_hunk: @@ -22,21 +20,33 @@ internal readonly ref struct PacketHandle
     {
         public const int NativePointerType = 1;
         public const int NativePacketType = 2;
+#if NET
path: src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs
user: login: benrr101
body: So, I saw a similar scenario in the SNIHandle class, and I'm not sure this is a good approach. My thoughts here is that this is a good example where we should have an abstract class and a ManagedPacketHandle and a NativePacketHandle. These are very lightweight classes that just wrap the actual packet handle, which makes me wonder if they're even necessary in the first place. So, my big question is: 
* option a, should we just merge it like this and maybe address it later
* option b, should we introduce some abstraction
* option c, should we consider removing the handle class altogether
created_at: 2025-05-19T21:39:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,27 +136,35 @@ internal void CreatePhysicalSNIHandle(
                 ipPreference, cachedDNSInfo, hostNameInCertificate);
         }
 
-        internal bool IsPacketEmpty(PacketHandle readPacket) => readPacket == default;
+        internal bool IsPacketEmpty(PacketHandle readPacket)
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: benrr101
body: It seems to me this is something that should be moved into the packet handle itself?
created_at: 2025-05-19T21:40:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -377,7 +383,13 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
 
         private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
         {
-            return SniNativeWrapper.SniPacketGetData(packet, _inBuff, ref dataSize);
+            return SniPacketGetData(packet, _inBuff, ref dataSize);
+        }
+
+        private uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize)
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: benrr101
body: To me this seems unnecessary - just put the contents of this method into GetSniPacket. But if you made this change because netcore does it this way, I'd say we should make netcore do it the simpler way.
created_at: 2025-05-19T21:42:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -790,7 +810,15 @@ private Task SNIWritePacket(SNIPacket packet, out uint sniError, bool canAccumul
 
 #pragma warning restore 420
 
-        internal bool IsValidPacket(PacketHandle packetPointer) => packetPointer != default;
+        internal bool IsValidPacket(PacketHandle packetPointer)
+        {
+            Debug.Assert(packetPointer.Type == PacketHandle.NativePointerType || packetPointer.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePointer");
+            return (
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: benrr101
body: nit: technically the parenthesis at the beginning here are redundant.
if it were me, I'd consider rewriting this like:
```
return (xyz && abc)
    || (xyz && abc);
```
created_at: 2025-05-19T21:45:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,21 +20,33 @@ internal readonly ref struct PacketHandle
     {
         public const int NativePointerType = 1;
         public const int NativePacketType = 2;
+#if NET
path: src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs
user: login: edwardneal
body: I think we can set option b aside - this is a ref struct, so it can't inherit anything. There might be a path to replace this via generics, but there's already a design gap between netcore and netfx. I think we should merge the type as-is, finish the merge of TdsParserStateObject and TdsParserStateObjectNative, then revisit it.
created_at: 2025-05-19T22:24:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -377,7 +383,13 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
 
         private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
         {
-            return SniNativeWrapper.SniPacketGetData(packet, _inBuff, ref dataSize);
+            return SniPacketGetData(packet, _inBuff, ref dataSize);
+        }
+
+        private uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize)
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: edwardneal
body: This needs to remain as-is, I'm afraid. In netcore, SniPacketGetData is an abstract method. This change syncs up the method definitions so they can be merged in #3355 - commit is [here](https://github.com/dotnet/SqlClient/pull/3355/commits/e06dba95519f717c59e207a174f9f5cb85b8ceb2).
created_at: 2025-05-21T04:25:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,27 +136,35 @@ internal void CreatePhysicalSNIHandle(
                 ipPreference, cachedDNSInfo, hostNameInCertificate);
         }
 
-        internal bool IsPacketEmpty(PacketHandle readPacket) => readPacket == default;
+        internal bool IsPacketEmpty(PacketHandle readPacket)
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: edwardneal
body: In principle, yes - but this is another abstract method which we merge in #3355, with implementations which vary between TdsParserStateObjectNative and TdsParserStateObjectManaged. The assertions verify that we're not taking a PacketHandle populated for the native SNI and passed it to the managed SNI (and vice versa.)
created_at: 2025-05-21T04:30:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -790,7 +810,15 @@ private Task SNIWritePacket(SNIPacket packet, out uint sniError, bool canAccumul
 
 #pragma warning restore 420
 
-        internal bool IsValidPacket(PacketHandle packetPointer) => packetPointer != default;
+        internal bool IsValidPacket(PacketHandle packetPointer)
+        {
+            Debug.Assert(packetPointer.Type == PacketHandle.NativePointerType || packetPointer.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePointer");
+            return (
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: edwardneal
body: Agreed. I'll make this change in #3355, when the method is ready to merge.
created_at: 2025-05-21T04:32:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,21 +20,33 @@ internal readonly ref struct PacketHandle
     {
         public const int NativePointerType = 1;
         public const int NativePacketType = 2;
+#if NET
path: src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs
user: login: benrr101
body: Ok, option A it is
I keep forgetting about ref structs ... kinda annoying technicalities imho
created_at: 2025-05-21T17:05:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,27 +136,35 @@ internal void CreatePhysicalSNIHandle(
                 ipPreference, cachedDNSInfo, hostNameInCertificate);
         }
 
-        internal bool IsPacketEmpty(PacketHandle readPacket) => readPacket == default;
+        internal bool IsPacketEmpty(PacketHandle readPacket)
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: benrr101
body: Hmm, ok... I guess the change I'm suggesting would make more sense if the packet handles were typed native and managed. But as you said above, we can't really do that today.
created_at: 2025-05-21T17:09:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -790,7 +810,15 @@ private Task SNIWritePacket(SNIPacket packet, out uint sniError, bool canAccumul
 
 #pragma warning restore 420
 
-        internal bool IsValidPacket(PacketHandle packetPointer) => packetPointer != default;
+        internal bool IsValidPacket(PacketHandle packetPointer)
+        {
+            Debug.Assert(packetPointer.Type == PacketHandle.NativePointerType || packetPointer.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePointer");
+            return (
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: benrr101
body: I'll try to hold you to it :)
created_at: 2025-05-21T17:09:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -377,7 +383,13 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error)
 
         private uint GetSniPacket(PacketHandle packet, ref uint dataSize)
         {
-            return SniNativeWrapper.SniPacketGetData(packet, _inBuff, ref dataSize);
+            return SniPacketGetData(packet, _inBuff, ref dataSize);
+        }
+
+        private uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize)
path: src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs
user: login: benrr101
body: Ok, i think that's ok for now
created_at: 2025-05-21T17:10:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: benrr101
created_at: 2025-05-19T21:29:54Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-19T21:30:20Z
body: <samp>
Azure Pipelines successfully started running 2 pipeline(s).<br>

</samp>
author_association: NONE
user: login: edwardneal
created_at: 2025-05-19T21:41:10Z
body: Thanks.

The PacketHandle and SessionHandle designs were added in dotnet/corefx#34044, and they were added to avoid boxing. This boxing was introduced as part of the original implementation of the managed SNI in dotnet/corefx#16589. The original native SNI design had the two types we care about (IntPtr and SNIPacket) and this has evolved from that.

To speculate on the original design pattern: it feels like we're driven partially by marshalling here. When a PacketHandle's `Type == NativePacketType`, we know that it's transporting an `SNIPacket` instance. This type is a SafeHandle derivative, and it's marshalled *into* the unmanaged SNI when we set a packet's data and write it out to the remote instance. Conversely, a PacketHandle's `Type == NativePointerType` when we know that it's transporting nothing more than an IntPtr. This is marshalled *out* of the unmanaged SNI when we receive a packet and read its data in managed code.

Strictly speaking, I'm not sure we'd need to maintain that separation; I'd personally be surprised if the unmanaged SNI DLL had a type separation between the inbound and the outbound packets, so the managed library might not need it either. If there's no separation, we might be able to rely on the normal SafeHandle marshalling mechanisms to let us use SNIPacket instances from end to end. This'd simplify matters, but it'd also increase GC traffic (because every packet received from SQL Server is now a full object with its own finalizer, rather than just an IntPtr.)

If we wanted to avoid that specific regression, another way of representing this might be a generics-based design similar to below:

```csharp
internal abstract class TdsParserStateObject<TOutputPacket, TInputPacket, TSession>
{
    public abstract TSession Session { get; }
    public abstract SetPacketData(TOutputPacket packet, byte[] buffer, int bytesUsed);
    public abstract void ReleasePacket(TInputPacket syncReadPacket);

    // This was in TdsParser, but would need to be be moved into TdsParserStateObject - it's the
    // only place outside this class (besides NativeSspiContextProvider) where PacketHandle and SessionHandle are used
    public void PostReadAsyncForMars();
    // ...
}

internal sealed class TdsParserStateObjectNative : TdsParserStateObject<SNIPacket, IntPtr, SNIHandle>
{ }

internal sealed class TdsParserStateObjectManaged : TdsParserStateObject<SNI.SNIPacket, SNI.SNIPacket, SNI.SNIHandle>
{ }
```

It might be worth considering and benchmarking after these are merged.
author_association: CONTRIBUTOR
user: login: benrr101
created_at: 2025-05-21T17:11:51Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-05-21T17:12:17Z
body: <samp>
Azure Pipelines successfully started running 2 pipeline(s).<br>

</samp>
author_association: NONE
user: login: codecov[bot]
created_at: 2025-05-21T20:41:36Z
body: ## [Codecov](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) Report
Attention: Patch coverage is `82.50000%` with `7 lines` in your changes missing coverage. Please review.
> Project coverage is 61.38%. Comparing base [(`dc1298a`)](https://app.codecov.io/gh/dotnet/SqlClient/commit/dc1298a0d3043202010ac8ab3d3152cfbf5d2c09?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) to head [(`1c38fd9`)](https://app.codecov.io/gh/dotnet/SqlClient/commit/1c38fd957c26f4c9182715efd134c15f42f3ec0b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet).
> Report is 8 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) | Patch % | Lines |
|---|---|---|
| [...osoft/Data/SqlClient/TdsParserStateObject.netfx.cs](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353?src=pr&el=tree&filepath=src%2FMicrosoft.Data.SqlClient%2Fnetfx%2Fsrc%2FMicrosoft%2FData%2FSqlClient%2FTdsParserStateObject.netfx.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet#diff-c3JjL01pY3Jvc29mdC5EYXRhLlNxbENsaWVudC9uZXRmeC9zcmMvTWljcm9zb2Z0L0RhdGEvU3FsQ2xpZW50L1Rkc1BhcnNlclN0YXRlT2JqZWN0Lm5ldGZ4LmNz) | 79.41% | [7 Missing :warning: ](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3353      +/-   ##
==========================================
- Coverage   65.16%   61.38%   -3.78%     
==========================================
  Files         300      294       -6     
  Lines       65379    65082     -297     
==========================================
- Hits        42606    39953    -2653     
- Misses      22773    25129    +2356     
```

| [Flag](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) | Coverage Δ | |
|---|---|---|
| [addons](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) | `?` | |
| [netcore](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) | `66.59% <ø> (-1.83%)` | :arrow_down: |
| [netfx](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet) | `59.59% <82.50%> (-6.74%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/dotnet/SqlClient/pull/3353?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dotnet).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE