test(operators): make throttle supersession tests deterministic#98
Merged
Conversation
- WhenThrottleReceivesRapidValues_ThenOnlyEmitsLatest completed the DirectSource from the test thread while the throttled emission was still unwinding on a pooled timer continuation. That overlap tripped WitnessAsync's concurrent-call guard, which silently drops the completion and left the test waiting on a never-signalled TCS until the 5s WaitAsync timed out. Drive completion re-entrantly from inside the emission handler so both notifications stay on one thread. - WhenThrottleUntilTrueFastReplacements_ThenLatestWins latched the first emission into a single-value TCS, so a wall-clock race where the earlier value's timer fired before the later value replaced it poisoned the result. Record all emissions and wait for the later value (whose timer is never superseded), then assert the final observed value is the latest. Test-only; no product behaviour changes.
ChrisPulman
approved these changes
Jun 25, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #98 +/- ##
==========================================
+ Coverage 90.79% 90.90% +0.10%
==========================================
Files 651 651
Lines 20455 20464 +9
Branches 2454 2454
==========================================
+ Hits 18573 18602 +29
+ Misses 1507 1490 -17
+ Partials 375 372 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
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.



What kind of change does this PR introduce?
Test-only fix for two flaky throttle supersession tests. No product code changes.
What is the new behavior?
Both tests now drive ordering with explicit synchronization instead of racing wall-clock timing:
WhenThrottleReceivesRapidValues_ThenOnlyEmitsLatest(Async.Tests) now completes theDirectSourcere-entrantly from inside the throttled emission handler. Because the emission and the completion then run on the same thread, theWitnessAsyncconcurrent-call guard is never tripped, so completion is always delivered. The redundantlastEmittedTCS is removed; the test now simply awaitscompleted.WhenThrottleUntilTrueFastReplacements_ThenLatestWins(Extensions.Tests) now records every emission and waits on the later value (whose throttle timer is never superseded), then asserts the final observed value is the latest one — instead of latching whichever emission happened to arrive first into a single-value TCS.What is the current behavior?
Both tests intermittently failed under CI load:
WhenThrottleReceivesRapidValues_ThenOnlyEmitsLatestfailed with a 5sTimeoutExceptionatawait completed.Task.WaitAsync(...). Root cause: the throttled value-3 emission runs on a pooled timer continuation (the non-systemTimeProviderpath usesPooledDelaySourcewithRunContinuationsAsynchronously = true). The test then calledsource.Complete(...)from the test thread. When that raced the still-unwindingOnNextAsynccall on the pool thread,WitnessAsync.TryEnterOnSomethingCalldetected a cross-thread concurrent call and silently dropped the completion, so thecompletedTCS was never signalled. This is a test harness race, not a product bug — the product correctly rejects concurrent witness calls.WhenThrottleUntilTrueFastReplacements_ThenLatestWinsfailed with "Expected to be 2 but found 1". Root cause:ThrottleUntilTrueuses a real wall-clock scheduler with a 50 ms window and exposes no scheduler seam. The two synchronousOnNextcalls are adjacent, but under thread starvation the earlier value's timer could fire before the later value replaced it, and the single-valueTrySetResultlatched the earlier value permanently.Reproduced the first flake locally under heavy CPU load (~1 failure in 30 runs with a
TimeoutException); both fixes then ran green 160/160 (primary) and 60/60 (secondary) under the same load.Not closing an issue.
What might this PR break?
None. Test-only change; no production code touched, no analyzer suppressions.
Checklist
mainbranchAdditional information
Repetition-loop results on net11.0 Release under ~40 concurrent CPU burners (16 cores):
WhenThrottleReceivesRapidValues_ThenOnlyEmitsLatest: pre-fix 1/30 failed (TimeoutException); post-fix 60/60 then 100/100 passed.WhenThrottleUntilTrueFastReplacements_ThenLatestWins: post-fix 60/60 passed.ReactiveUI.Primitives.Async.TestsandReactiveUI.Primitives.Extensions.Testsprojects pass on net11.0.