Files
lmxopcua/scripts/validation/opcua-lifecycle-memory/windows
Joseph Doherty fea7de0aef
v2-ci / unit-tests (push) Waiting to run
docs(evidence): #799 acceptance runs on master aa60b3e3 - Windows x64 and Linux both fail the unchanged cold RSS cap
Owner scope 2026-11-12 (Windows x64 + Linux, unchanged cycle-3 caps, >=7200 s, SDK 1.5 comparator).

- Linux ARM64 (late-slope, 7200.12 s, 1964 cycles / 491 restarts, shutdown verified, analyzer-validated):
  first rejection RSS at cycle 247 / 900.73 s, 358,682,624 > 358,678,528 (+4 KiB); peak 382.98 MiB vs
  limit 342.06 MiB; late plateau medians 344.4/341.1/341.9 MiB, OLS -1.90 MiB/h. FAIL.
- Windows x64 on windev (same workload, working set as RSS, handle table as descriptors): first rejection RSS
  at cycle 285 / 1130.76 s, 303,505,408 > 302,174,208; peak 319.49 vs limit 288.18 MiB; plateau ~314-315 MiB,
  +0.93 MiB/h. FAIL. Also incomplete: the launcher's wall-clock outer deadline fired early after a +552 s
  w32time step and ended the run at test-phase 7014 s (rejection had already latched); launchers now use a
  Stopwatch.
- SDK 1.5.378.182 comparator (efb9197a publication, Windows, 7201 s): RSS peak +65.8 MiB (never over its cap),
  plateau ~+48 MiB, but the +32 handle cap tripped at cycle 17 (max 674 vs 642).
- Managed flat on both SDK2 runs; growth is the generated-factory JIT burst (+40k/+44k methods) - Linux JIT
  doublemapper ~180 MiB; Windows private bytes grew ~25 MiB of a ~132 MiB working-set rise.
- Retained: b61f3e05 cycle-1 fixture failure (#808 unplaced UNS folder), Windows/SDK1 smoke diagnostics.

No cap raised, no warm baseline, no failed run counted as passing.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
2026-11-12 14:51:31 -05:00
..

Windows x64 late-slope soak (lmxopcua#799 acceptance)

Owner scope 2026-11-12: #799 acceptance covers the deployment platforms, Windows x64 and Linux. This folder runs the same canonical test as the Linux late-slope run (Sdk2LifecycleSoakTests.Repeated_lifecycles_return_resources_to_a_steady_baseline, OTOPCUA_SDK2_SOAK_MODE=late-slope, 7,200 s) on Windows x64 with the protocol unchanged: cycle-3 cold baseline, +64 MiB retained managed / +128 MiB RSS / +32 descriptors, first-rejection latch with every canonical cycle and restart continuing, ownership/logical/operation/shutdown failures fatal, certificate leak tracking on, the same startup/cycle/restart/shutdown bounds, and the same fixed windows and least-squares report (analyze.py imports ../late-slope/analyze.py and only switches its expected architecture to X64).

Windows adaptations

Linux element Windows equivalent Why
RSS = Process.WorkingSet64 (= VmRSS) Working set, the same Process.WorkingSet64 call, unchanged test code Resident physical pages of the process, the direct analog of Linux RSS. Private bytes (commit charge) counts committed-but-not-resident memory and pagefile-backed pages, so it is not comparable to RSS; the launcher records it externally every 30 s as a descriptive series only.
Descriptors = entries of /proc/self/fd Every handle in the process handle table (NtQuerySystemInformation(SystemExtendedHandleInformation), own PID) Windows has no fd table; the handle table is the per-process kernel-resource table. It is broader than Linux fds (threads, events, keys, sections), so the unchanged +32 cap is if anything stricter.
PKI descriptors = fds whose link target lies under a PKI root Disk-file/directory handles (GetFileType = DISK) whose GetFinalPathNameByHandle path lies under a root; roots canonicalised the same way, compared case-insensitively Final paths resolve symlinks, junctions, 8.3 names and casing (the Windows analog of the macOS /var→/private/var lesson).
Probe bound: lsof child 5 s Enumeration on a dedicated thread, 5 s bound; a blocked name query is a TimeoutException, never a favourable zero Name queries can block on synchronous handles; only disk handles are name-queried.
Descriptor controls (symlink alias open/close; probe timeout reaps /bin/sh) Same two tests, Windows enabled: Directory.CreateSymbolicLink alias (Developer Mode / elevated); the timeout control runs cmd.exe holding both pipes open behind a 30 s ping Must execute 2/2 before the canonical test starts, as in container.sh.
Container: read-only root, --network none, private /task Fresh private task directory (inheritance removed; user + SYSTEM) holding TEMP/TMP, CLI home, results and fixture PKI; loopback-only fixture; machine otherwise idle No Windows container runtime on the deployment-class host.
/proc/<pid>/exe + mapped libcoreclr.so check Target PID's image must be the bundle …IntegrationTests.exe and coreclr.dll must be mapped from the bundle with the receipt hash Same proof that the self-contained 10.0.14 runtime is the one measured.
Container outer deadline 7,440 s Launcher kills only the task-owned process tree at 7,440 s Same bound.
— External sampler: working set, private bytes, handle count, threads every 30 s; MxAccessGw readiness every 5 min Descriptive diagnostics and proof the co-hosted service stayed healthy. Not gated.

LateSlopeRun.ValidateRequest accepts Linux ARM64 or Windows x64 (owner scope); the 7,200 s duration, runtime 10.0.14 and leak-tracking requirements are unchanged.

Run

python3 scripts/validation/opcua-lifecycle-memory/windows/prepare.py --source-revision "$rev" --output "$bundle"
# copy the bundle to the Windows host (COPYFILE_DISABLE=1 tar on macOS), then on the host:
powershell -NoProfile -ExecutionPolicy Bypass -File "$bundle\tools\launch.ps1" -Bundle "$bundle" -Output "$run" -SourceRevision "$rev"
# copy the run back, then:
python3 "$bundle/tools/analyze.py" --run "$run" --bundle-receipt "$bundle/receipt.json" --source-revision "$rev"

Launch the PowerShell launcher detached from any SSH session (e.g. Win32_Process.Create), because Win32-OpenSSH terminates a session's job object on disconnect.