Files
lmxopcua/scripts/validation/opcua-lifecycle-memory/sdk1-comparator/WindowsDescriptors.cs
T
Joseph Doherty 2e9cccbdef
v2-ci / unit-tests (push) Has been cancelled
test(soak): #799 SDK 1.5.378.182 comparator for the late-slope workload
Copies the reviewed baseline-canonical SDK1 port and replaces its 1..4-cycle smoke entry with the late-slope
loop (same cycle body, every-fourth-cycle active restart, 30/25/120 s bounds, 7200 s phase, cycle-3 cold
baseline with unchanged +64 MiB managed / +128 MiB RSS / +32 descriptor caps, latched first rejection,
bounded shutdown checks). Run against the immutable efb9197a publication (#807 fix included), never rebuilt.
The held-reference closed-session list assertion is recorded instead (SDK1 Close keeps lists until Dispose,
per OWNERSHIP.md) and the managed-monotonic guard is latched, so the comparator yields numbers for the whole
phase. acceptance=false by construction. Windows descriptor observer shared byte-for-byte with the SDK2 test.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
2026-11-12 12:48:44 -05:00

133 lines
6.3 KiB
C#

using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Microsoft.Win32.SafeHandles;
/// <summary>Byte-for-byte the Windows descriptor observer of Sdk2LifecycleWindowsDescriptors.cs (lmxopcua#799), hosted for the SDK1 comparator.</summary>
internal static class WindowsDescriptors
{
[SupportedOSPlatform("windows")]
internal static Task<(int Total, int Pki)> CountAsync(CancellationToken ct, IReadOnlyList<string> roots)
{
var result = new TaskCompletionSource<(int Total, int Pki)>(TaskCreationOptions.RunContinuationsAsynchronously);
var thread = new Thread(() =>
{
try { result.TrySetResult(WindowsHandleCounts(roots)); }
catch (Exception error) { result.TrySetException(error); }
}) { IsBackground = true, Name = "soak-descriptor-probe" };
thread.Start();
return BoundAsync();
async Task<(int Total, int Pki)> BoundAsync()
{
try { return await result.Task.WaitAsync(TimeSpan.FromSeconds(5), ct).ConfigureAwait(false); }
catch (TimeoutException) { throw new TimeoutException("Descriptor probe exceeded its handle-enumeration deadline."); }
}
}
[SupportedOSPlatform("windows")]
private static (int Total, int Pki) WindowsHandleCounts(IReadOnlyList<string> roots)
{
// Canonicalise the roots first and close those directory handles before the snapshot, so they are not counted.
var canonicalRoots = roots.Select(WindowsCanonicalRoot).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
var self = (nuint)Environment.ProcessId;
var length = 1 << 20;
while (true)
{
var buffer = Marshal.AllocHGlobal(length);
try
{
var status = NtQuerySystemInformation(SystemExtendedHandleInformation, buffer, length, out var needed);
if (status == StatusInfoLengthMismatch)
{
length = Math.Max(length * 2, needed + (1 << 16));
continue;
}
if (status != 0) throw new Win32Exception(RtlNtStatusToDosError(status), $"NtQuerySystemInformation failed (0x{status:X8}).");
var count = (long)Marshal.ReadIntPtr(buffer);
var entries = buffer + 2 * IntPtr.Size;
var entrySize = Marshal.SizeOf<HandleEntry>();
int total = 0, pki = 0;
var path = new char[32768];
for (long i = 0; i < count; i++)
{
var entry = Marshal.PtrToStructure<HandleEntry>(entries + (nint)(i * entrySize));
if (entry.UniqueProcessId != self) continue;
total++;
var handle = (nint)entry.HandleValue;
// GetFileType is answered by the I/O manager without the file-object lock; only disk files and
// directories go on to the name query (pipes, sockets and consoles cannot hold PKI material).
if (GetFileType(handle) != FileTypeDisk) continue;
var name = FinalPath(handle, path);
if (name is not null && WithinWindowsRoots(name, canonicalRoots)) pki++;
}
return (total, pki);
}
finally { Marshal.FreeHGlobal(buffer); }
}
}
[SupportedOSPlatform("windows")]
private static string WindowsCanonicalRoot(string root)
{
var full = Path.TrimEndingDirectorySeparator(Path.GetFullPath(root));
if (!Directory.Exists(full)) return BaselineMetrics.CanonicalPath(full);
using var directory = CreateFileW(full, 0, FileShareAll, 0, OpenExisting, FileFlagBackupSemantics, 0);
if (directory.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error(), "Cannot open a PKI root for canonicalisation.");
return FinalPath(directory.DangerousGetHandle(), new char[32768])
?? throw new IOException("A PKI root has no final path.");
}
private static string? FinalPath(nint handle, char[] buffer)
{
var written = GetFinalPathNameByHandleW(handle, buffer, buffer.Length, 0);
if (written == 0 || written >= buffer.Length) return null; // Closed/reused between snapshot and query, or not a named file.
var name = new string(buffer, 0, (int)written);
if (name.StartsWith(@"\\?\UNC\", StringComparison.Ordinal)) return @"\\" + name[8..];
return name.StartsWith(@"\\?\", StringComparison.Ordinal) ? name[4..] : name;
}
private static bool WithinWindowsRoots(string path, IReadOnlyList<string> roots)
{
path = Path.TrimEndingDirectorySeparator(path);
return roots.Any(root => path.Equals(root, StringComparison.OrdinalIgnoreCase)
|| path.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase));
}
private const int SystemExtendedHandleInformation = 64;
private const int StatusInfoLengthMismatch = unchecked((int)0xC0000004);
private const uint FileTypeDisk = 1;
private const uint FileShareAll = 7;
private const uint OpenExisting = 3;
private const uint FileFlagBackupSemantics = 0x02000000;
[StructLayout(LayoutKind.Sequential)]
private struct HandleEntry
{
public nint Object;
public nuint UniqueProcessId;
public nuint HandleValue;
public uint GrantedAccess;
public ushort CreatorBackTraceIndex;
public ushort ObjectTypeIndex;
public uint HandleAttributes;
public uint Reserved;
}
[DllImport("ntdll.dll")]
private static extern int NtQuerySystemInformation(int informationClass, nint information, int length, out int returnLength);
[DllImport("ntdll.dll")]
private static extern int RtlNtStatusToDosError(int status);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetFileType(nint handle);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint GetFinalPathNameByHandleW(nint handle, [Out] char[] path, int length, uint flags);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern SafeFileHandle CreateFileW(string name, uint access, uint share, nint security, uint disposition,
uint flags, nint template);
}