Skip to content

Investigate intermittent allocations in the profiler recording-only regression test #168

Open

Description

@ProGraMajster

Problem and expected contract

PerformanceProfilerTests.RecordingOnlyHotPathDoesNotAllocateAfterSourceWarmup intermittently fails the required Release build check. The contract remains 0 managed bytes on the current thread after source warmup. Do not increase the threshold or add retries/skips to conceal the failure.

This is a separate investigation from #167 / #161 and from BrushInterpolationCompatibilityTests.PreparedPlanDoesNotAllocatePerIntermediateFrame. Related diagnostics area: #58.

Confirmed CI evidence predating #161

Current CI configuration

Windows Server 2025 Datacenter 10.0.26100 x64, image windows-2025-vs2026 version 20260925.250.1, SDK 10.0.401, runtime 10.0.12, MSBuild 18.9.11. Release build followed by:

dotnet test ModernFormsNext.slnx --configuration Release --no-build --no-restore -m:1 /p:UseSharedCompilation=false --logger "trx;LogFilePrefix=ci"

No workflow retry, coverage collection or GC/JIT override. Projects are sequential; xUnit classes in the framework test project may run concurrently. The failure occurred about four seconds into that project's test host, duration 8 ms. The logs contain no allocation stack or exact passing-test order; no uploaded TRX artifact was retained.

Exact-base / PR local comparison

Windows 10.0.26300 x64, same SDK/runtime; 32 logical CPUs. Both versions used Release, sequential MSBuild, no-build/no-restore and the unchanged original assertion. Each repetition below starts a new test host. A passing result means the unchanged assertion observed 0 B, not a rounded or tolerated value.

Experiment Exact base 984fbca PR 8d7d883
Isolated test, normal runtime 20/20 processes PASS 20/20 processes PASS
Full PerformanceProfilerTests class (10 tests) 10/10 processes PASS 10/10 processes PASS
Full framework project, normal CPU count 3/3, 1481 tests each 3/3, 1482 tests each
Full framework project, DOTNET_PROCESSOR_COUNT=4 2/2, 1481 each 2/2, 1482 each
Isolated test, 4 CPUs, default runtime 5/5 5/5
Isolated test, 4 CPUs, tiered compilation off 5/5 5/5
Isolated test, 4 CPUs, TieredPGO off 5/5 5/5
Isolated test, 4 CPUs, ReadyToRun off 5/5 5/5
Isolated test, 4 CPUs, Server GC 5/5 5/5

Additionally: PR isolated test with VSTest diagnostic logging 20/20; PR full Release solution at 4 CPUs 4073/4073 PASS, 0 skipped. Current master equaled exact base at investigation time. No current local reproduction of the original test failure was obtained on either revision. This is historical pre-#161 reproduction evidence, not a claim that the current exact-base local matrix failed.

Order, state and thread findings

  • No deterministic predecessor was found. In local full-project TRX, the preceding completed test was usually CopiedScopesAndRetiredResourcesCannotAffectAnotherSession; other classes overlap. The exact CI order is unavailable, so order dependence is not excluded.
  • The workload is synchronous: 100 warmup renders, then 1000 measured renders between GC.GetAllocatedBytesForCurrentThread() calls. It has no await/dispatcher continuation.
  • An instrumented copy of the xUnit test, kept outside tracked source, recorded the same managed thread throughout all warmup/measured iterations on base and PR, 0 B, and no active profiler before/after disposal. No global-state leak was reproduced.
  • Profiler and platform diagnostic registrations are thread-static. Setup allocates pre-sized buffers and dictionaries; first source/root entries are warmed. The measured recording path uses structs/preallocated buffers, timestamp callbacks and counters. Snapshot/export/logging/observer publication are outside the measured path.
  • The 100 iterations exercise source-level first-use branches, but do not prove that all CLR lazy work or tiered compilation has finished. The runtime variants all passed even with defaults; they therefore do not attribute the CI allocation to JIT/PGO/GC.
  • A separate top-level diagnostic harness showed positive outer-loop deltas on the base while every per-Render delta was zero. Moving each session into a non-inlined method yielded 30/30 zero-byte sessions on both versions; direct calls to the unchanged compiled original test passed 50/50 on each. This harness anomaly is retained as diagnostic evidence but is not counted as reproduction of the CI failure or proof of profiler allocations.

Relationship to #161 and confidence

#167 does not modify the profiler/test/workflow/runtime setup. The measured workload does not call validation, binding or focus transactions. Added Control static keys initialize before the warmup. New tests can still change scheduling/initialization timing; the one new framework-project test can affect collection order, while most #161 tests run in another project's process. An indirect timing effect cannot be ruled out merely from an unchanged file diff.

High confidence: this test's failure pattern predates #161. No evidence of a deterministic #161-specific regression was found. Exact root cause and allocation type/stack remain unknown. Environment/timing-sensitive runtime or first-use work is a hypothesis, not an established runtime defect; a real profiler allocation or order-dependent interaction remains to be excluded.

Scope and acceptance for a separate fix

  1. Capture allocation origin on a failing unmodified Release test under representative Windows CI load, with diagnostics outside the measured region and controls for instrumentation effects. Allocation sampling can miss a few kilobytes, so absence of samples alone is insufficient.
  2. If order-dependent, identify the minimal predecessor and leaked state; if runtime/first-use-dependent, identify the exact allocating branch/type and demonstrate it separately from probe overhead.
  3. Fix the proven production or measurement-isolation root cause in a separate PR. Preserve the zero-allocation contract, required build context, normal runtime configuration and assertions; no blind warmup extension, tolerance, retry, sleep or skip.
  4. Validate the reproduction before/after, 20 fresh processes on base/fix, full class/project and normal full Release CI. Retain the original failures in the report.

One normal rerun of 37206449059 was initiated only after the above historical and local/base evidence was collected. It uses the unchanged head and workflow. Attempt 2 / job 111458432849 completed SUCCESS at 2026-10-04 14:45:41 UTC: 4073/4073 PASS, 0 skipped, same head, SDK/runtime, workflow and all nine project totals. The original zero-byte assertion passed; successful-test bytes are not separately printed in CI. No additional rerun was performed. This success does not establish the allocation source. PR #167 remains open and unmerged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions