Repository navigation
Lite and Darling WPF tests take one gate so parallel STA classes stop racing a shared WPF cache (#5559) - #5567
Merged
Merged
Conversation
erikdarlingdata
marked this pull request as ready for review
October 8, 2026 13:35
This was referenced Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Test-only fix for the parallel-STA flake in the Lite and Darling WPF tests.
SECTION: None
Why
Two CI attempts of the same Lite shard failed with
InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. The class differed each time. The stack ran throughAccessibleNames.ActivateCellandDependencyPropertyDescriptor.AddValueChanged.The cause is confirmed in the WPF source (dotnet/wpf, WindowsBase).
DependencyPropertyDescriptor.FromPropertyreturns a descriptor out of a process-wide static cache. Every STA thread therefore gets the sameDependencyObjectPropertyDescriptor. ItsAddValueChangedcalls_trackers.TryGetValue(...)and_trackers.Add(...)on a plain instanceDictionarywith no lock.The app has one UI thread, so the product is not exposed. xUnit runs test classes in parallel. 26 Lite.Tests classes and 25 Darling.Tests classes start their own STA thread. Two of them inside
ActivateCellat once corrupt that dictionary, and the corruption lasts for the rest of the process.What changes
Darling/Darling.Tests/WpfStaGate.csis new and is linked into Lite.Tests like the other shared test files. It holds oneSemaphoreSlim(1,1), taken withusing var staGate = WpfStaGate.Enter();.Join(60s). Taking the gate on the caller means the wait for it is not charged to that timeout. No other line in those files changed.WpfStaGateCensusTestsin each project is taggedStage=Guard. It fails and names any test file with an STA start that has no gate entry before it. It has a floor of 20 STA files, so a broken scan fails too. It also holds a small test that three threads through the gate never overlap..github/workflows/build.ymllists the new linked file in thelite_linked_shardpaths.DarlingReadsTraitGuardTestspins that list to the project's linked compiles.No guard, assertion, timeout or skip was changed. Some of these classes already carry another
[Collection](server-time-helper, app-logger-statics). A class cannot join a second xUnit collection, which is why this is a gate and not a collection.Test plan
AddValueChanged20000 times on the sharedContentControl.ContentProperty/DataGridCelldescriptor. Ungated, it failed with the CI message in 5 of 6 runs, about 0.25 s each. With a lock around the call it passed 3 of 3. I ran each alone, because a failed run leaves the shared dictionary corrupted for the process.EveryStaThreadStart_IsPrecededByItsOwnGateEntryfail and name every file, in both projects. WithEnter()made a no-op,TheGate_LetsOnlyOneHolderIn_AtATimefails. I restored both and the tree was clean.*Census*and*Ratchet*: Lite.Tests Total 60, Failed 0. Darling.Tests Total 492, Failed 0, Skipped 1 (live).-trait Stage=Guard -failSkips: Lite.Tests Total 443, Failed 0. Darling.Tests Total 1411, Failed 0.DarlingReadsTraitGuardTestsfailed until the new linked file was in thebuild.ymllist.Notes for the seat: I passed the wrong project path to my own runner once, and it ran the whole Darling.Tests suite. Two classes failed there:
SensitiveStatementAutoParamTests(a timing measure) andDiagnosticsBundleHardeningTests. Both pass alone (Total 80, Failed 0), so I read them as load-sensitive and unrelated. The gate does not cover non-STA test code that touches WPF types from other threads. The census only sees STA thread starts.Fixes #5559