Skip to content

Code Health | Make SqlDependencyProcessDispatcherStorage not unsafe #3208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 24, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,24 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;

namespace Interop.Windows.Sni
{
internal unsafe class SqlDependencyProcessDispatcherStorage
internal class SqlDependencyProcessDispatcherStorage
{
private static void* s_data;
private static readonly object s_lockObj = new();
private static IntPtr s_data;
private static int s_size;
private static volatile int s_lock; // Int used for a spin-lock.

[ResourceExposure(ResourceScope.Process)] // SxS: there is no way to set scope = Instance, using Process which is wider
[ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
public static byte[] NativeGetData()
{
IntPtr ptr = (IntPtr)s_data;

byte[] result = null;
if (ptr != IntPtr.Zero)
if (s_data != IntPtr.Zero)
{
result = new byte[s_size];
Marshal.Copy(ptr, result, 0, s_size);
Marshal.Copy(s_data, result, 0, s_size);
}

return result;
Expand All @@ -38,29 +35,18 @@ public static byte[] NativeGetData()
[ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
internal static void NativeSetData(byte[] data)
{
fixed (byte* pDispatcher = data)
lock (s_lockObj)
{
while (Interlocked.CompareExchange(ref s_lock, 1, 0) != 0)
if (s_data == IntPtr.Zero)
{
// Spin until we have the lock.
Thread.Sleep(50); // Sleep with short-timeout to prevent starvation.
}
Trace.Assert(s_lock == 1); // Now that we have the lock, lock should be equal to 1.

if (s_data == null)
{
s_data = Marshal.AllocHGlobal(data.Length).ToPointer();

Trace.Assert(s_data != null);

Buffer.MemoryCopy(pDispatcher, s_data, data.Length, data.Length);

Trace.Assert(0 == s_size); // Size should still be zero at this point.
s_data = Marshal.AllocHGlobal(data.Length);
Trace.Assert(s_data != IntPtr.Zero);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this Assert() is helpful. AllocHGlobal() either allocates memory successfully and returns an IntPtr to it, or it throws. Granted, the AllocHGlobal() docs don't explicitly say the returned IntPtr.Zero will never be true, but so what if it is? Will Copy() work if s_data.Zero is true? Those docs don't say either way, so I'm not sure we should be making any assumptions here. I think this Assert() gives a false sense of program state below (i.e. s_data.Zero == false). If it's important that we never call Copy() with a Zero IntPtr, then we need an if-statement.

This is just FYI - no need to make any changes - just wanted to point it out.


Marshal.Copy(data, 0, s_data, data.Length);

Trace.Assert(s_size == 0); // Size should still be zero at this point
s_size = data.Length;
}

int result = Interlocked.CompareExchange(ref s_lock, 0, 1);
Trace.Assert(1 == result); // The release of the lock should have been successful.
}
}
}
Expand Down
Loading