Repository navigation
Cut STAThreadTests repeats so the Windows reflection pass run fits its timeout - #7594
Conversation
…s timeout The Windows [EngineTest=Pass] reflection run was not hung: in both captured dumps the process's CPU time tracked its progress, and the newest capture was in the final NotInParallel phase. The run is CPU bound, and STAThreadTests with [Repeat(100)] used about 120 of its 140 CPU seconds, because each test and each of the ~55 global per-test hooks runs on a new STA thread. On the 4-core Windows runner that pushed the run past its 10-minute timeout. Repeat(10) keeps every STA scenario and cuts the run on 4 local cores from 139 s CPU / 2m33s to 30 s CPU / 1m48s. Fixes #7554
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
[Low impact] This PR appears safe to merge. SummaryReduces
Reviews (1) · Last reviewed commit: "Cut STAThreadTests repeats so the Window..." · Reviewed by Greptile |
|
Review: LGTM. One-line change reducing Optional, non-blocking: the root cause is that each STA execution spawns a new thread for every global hook. A follow-up could reuse a single STA thread per test (running its hooks on it) in |
Fixes #7554.
Diagnosis
The Windows
[EngineTest=Pass]reflection run is slow, not hung. I analyzed the raw hang dumps from runs 41849691180 (the one #7584 analyzed) and 41860407100 (the latest #7584 head) locally withdotnet-dump, plus the minidump thread and process CPU times:_6885Test3 just started)A complete local run uses about 140 s of CPU, so in both dumps the CPU time matches the progress. The newest dump has no blocked chain:
dumpasyncshows onlyDedicatedThreadExecutorCleanUpOrderTests.Test3awaiting itsTask.Delay(400). The two long-running names in the hang log,RunandInstanceMethodDataSource_DoesNotReuseEnumerationInstance, are deferred placeholders that are reported as running for the whole session (see the issue thread). TheThread.StartInternal/PulseAllHelperpairs in the older dump are threads starting and exiting under CPU starvation (the machine was at 100%), not a deadlock: every target thread had already run its body (_startHelperis null andTS_Unstartedis cleared).A passing Windows run already takes 7m12s (run 41856853804), against the 8-minute dump and the 10-minute timeout. Linux and macOS finish in 2 to 4 minutes.
Cause
STAThreadTestsis Windows-only and has[Repeat(100)], which gives 1,515 tests.STAThreadExecutoralso runs every hook for those tests, and the project has about 55 global[BeforeEvery(Test)]/[AfterEvery(Test)]hooks. Each test and each hook runs on its own new STA thread, so this class starts about 78,000 STA threads. A local harness measured about 1.2 ms of CPU (mostly kernel) for each STA execution.Measured on 4 pinned cores locally (
DOTNET_PROCESSOR_COUNT=4, affinity 0xF), with the Pass filter in reflection mode:STAThreadTests[Repeat(10)](this PR)On the shared Windows runner this run gets only part of the CPU, so cutting the CPU it needs by about 4.6x cuts its wall time by about the same factor.
Change
STAThreadTestschanges from[Repeat(100)]to[Repeat(10)]. Every STA scenario still runs 11 times, and the session timeout is unchanged. A comment records why the count must stay low.Validation
STAThreadTestspasses with 165 tests in both source-generated and reflection modes (--treenode-filter /*/*/STAThreadTests/*)./**[EngineTest=Pass]reflection run on 4 local cores passes with 5,417 tests.TUnit.Engine.Testslocally. The Windows engine job in CI checks the full session.Out of scope
I also tried two other changes. Neither made a measurable difference, so neither is included:
SpinWait.SpinOnce(which callsSleep(1)) from theDedicatedThreadExecutorpumpHookGeneratorTest.AfterEachTestTask.Delay(1)withTask.Yield()Summary by CodeRabbit