Acceptance runs for #799 (owner scope 2026-11-12: Windows x64 + Linux) on current master. Fixture: since #808 an UNS folder the authority snapshot does not place is denied under enforcement (fail closed). The canonical soak creates its own "SoakEquipment" folder but never placed it, so on master the very first cycle's UNS monitored item came back bad (retained failed Linux launch onb61f3e05, cycle 1). The rig gains AdditionalFolderScopes (merged into FolderScopes by Enforce) and the soak places its folder there, as a deployed generation does - the same fixture gap #888 fixed for the SDK matrix. No server authorization code, cap, baseline or canonical operation changed; the soak test bodies are otherwise byte-identical tob18f9723. Windows port (test/harness only, opt-in): - DescriptorCountsAsync gains a Windows branch: total = every handle in the process handle table, PKI = disk file/directory handles whose GetFinalPathNameByHandle path lies under a canonicalised PKI root; dedicated thread under the same 5 s bound as the macOS lsof probe. - Descriptor controls enabled on Windows (symlink alias; timeout control via cmd.exe + ping). - LateSlopeRun.ValidateRequest accepts Linux ARM64 or Windows x64; duration/runtime/leak tracking unchanged. - late-slope/analyze.py: expected architecture is a module constant (Linux default Arm64 unchanged). - windows/: prepare.py (win-x64 self-contained 10.0.14 bundle + manifest), launch.ps1 (container.sh equivalent: bundle re-verification, 2/2 controls first, PID/apphost/coreclr verification, external sampler, 7440 s outer deadline), analyze.py (Linux row/window logic, X64), README with every adaptation. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
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.