Commit 8033135
Wait for quiescence in MergeManyChangeSets stress tests (reactivemarbles#1100)
* Make MergeManyChangeSetsCacheSourceCompare stress test deterministic
MultiThreadedStressTest(10, 50) fails intermittently in CI with two prices
present in market.PricesCache.Items but missing from the live aggregator.
The two affected prices have the latest timestamps in the batch, which is
the signature of a race during high-contention production.
Bogus.Randomizer wraps System.Random. When constructed with a seed, the
randomizer stores the random in a protected localSeed field and bypasses
its internal Locker on every generator call. The test shares one seeded
Randomizer across many parallel producer threads:
- Directly via _randomizer.Number / .Bool / .TimeSpan / .Interval
- Indirectly via _marketFaker.WithSeed(_randomizer), since every
Faker<T>.Generate call routes through the same randomizer
Concurrent calls into the underlying System.Random corrupt its internal
state, producing values inconsistent with what a serialized run would
produce. That is sufficient to explain the observed asymmetry between
the post-hoc PricesCache snapshot and the live aggregator stream.
Introduce SynchronizedRandomizer, a Randomizer subclass that replaces
the protected localSeed field with a LockedRandom (a Random subclass that
serializes every virtual method on an internal lock). The seed and method
contracts are unchanged; the wrapper only adds synchronization.
Apply it to the failing fixture. Other Randomizer uses across the test
project remain unchanged for now; they are either single-threaded or
have not exhibited flake symptoms.
Verified: 20 consecutive runs of the fixture pass at MaxParallelThreads=16,
zero failures.
* Wait for quiescence in MergeManyChangeSets stress tests
The post-reactivemarbles#1079 cache delivery model decouples mutation from notification:
AddOrUpdate enqueues a notification and returns; the actual delivery to
subscribers runs later on whichever thread wins the drain. That removed
the cross-cache deadlock the old Synchronize(lock) shape produced, but it
opened a small window between mutation and observed delivery. Tests that
compare a live aggregator's view against the cache's current Items at
assert time can see disagreement during that window.
The source-compare fixture already adopted the right shape:
var merged = source.MergeManyChangeSets(...).Publish();
var cacheCompleted = merged.LastOrDefaultAsync().ToTask();
using var local = merged.AsAggregator();
using var connect = merged.Connect();
...
await cacheCompleted;
CheckResultContents(..., local);
Port the same pattern to the cache and list MergeManyChangeSets stress
fixtures. The local aggregator now sits on the Publish chain so it shares
the completion task; the await before CheckResultContents pins the
quiescence point.
Also delete the SynchronizedRandomizer change made earlier on this branch.
Bogus.Randomizer takes a process-wide lock on Locker.Value for every
generator call regardless of whether localSeed is set, so the wrapper was
addressing a non-problem.
---------
Co-authored-by: Darrin Cullop <dacullop@microsoft.com>1 parent 46ccd1a commit 8033135
2 files changed
Lines changed: 19 additions & 7 deletions
File tree
- src/DynamicData.Tests/Cache
Lines changed: 8 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
90 | 91 | | |
91 | 92 | | |
92 | 93 | | |
93 | | - | |
94 | | - | |
95 | | - | |
| 94 | + | |
96 | 95 | | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
97 | 99 | | |
98 | 100 | | |
99 | 101 | | |
| |||
119 | 121 | | |
120 | 122 | | |
121 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
122 | 127 | | |
123 | 128 | | |
124 | 129 | | |
| |||
Lines changed: 11 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
86 | 87 | | |
87 | 88 | | |
88 | 89 | | |
89 | | - | |
90 | | - | |
| 90 | + | |
91 | 91 | | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
92 | 95 | | |
93 | 96 | | |
94 | 97 | | |
| |||
114 | 117 | | |
115 | 118 | | |
116 | 119 | | |
117 | | - | |
118 | | - | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
119 | 126 | | |
120 | 127 | | |
121 | 128 | | |
| |||
0 commit comments