Files
romanett 40288fa7d8 Tests: weighted FairRequestQueue test asserts the median of three windows (#4763)
# Description

`FairRequestQueueTests.WeightedServiceHoldsUnderConcurrentWorkersAsync`
failed once on GitHub Actions macOS net10.0 (#4753, run 38349863236):
light=124 heavy=276, ratio 2.23 against an expected 2.5-3.5. It passed
on every other CI run and 30/30 locally.

**Root cause.** `TrySelect` dequeues an owner's turn token from the
lock-free ready queue. On the last token of a turn, `TakeTurnToken`
CASes the turn and calls `PushTurn` for the next one. A worker preempted
between those two steps leaves the owner with no queued token while it
is stalled. The work-conserving dispatch serves the other owner in the
meantime, and that share is never made up. Delay injection confirms
this: sleeping 1 ms on every 7th turn publish collapses 1:3 to roughly
1:1 (light=199 heavy=201).

Publishing a turn ahead of time would only cover stalls shorter than one
round (microseconds), while real preemption lasts milliseconds. Making
up the lost share (deficit or virtual time) would mean redesigning the
scheduler. So this PR documents the bounded skew and makes the test
robust instead.

**Changes**
- `FairRequestQueue` remarks: the weighting holds over time, not in
every window, and a preempted worker can cost an owner its share for the
length of the stall.
- `WeightedServiceHoldsUnderConcurrentWorkersAsync` now measures 3
independent windows through `MeasureWeightedServiceAsync` and asserts
that the **median** heavy/light ratio is in 2.5-3.5. The failure message
lists all three windows.
- Each window uses perOwner = 480 and measured = 560, with queue limits
of `2 * perOwner`. That stays under the TestProvider's 1000-request cap
and keeps both owners backlogged.
- Each window still checks that both owners stay backlogged, then drains
the queue and checks that everything is back to zero.

The median tolerates one skewed window and still fails when the
weighting is lost systematically. With stalls injected into one window
only, the median is 3.0 and the test passes (e.g. `light=238 heavy=322;
light=140 heavy=420; light=140 heavy=420`). With stalls injected into
every window, it still fails.

**Validation:** the `FairRequestQueueTests` fixture passes 93/93 on
net10.0 Release. The weighted test passed 30/30 runs pinned to 2 cores
(`start /affinity 3`). `dotnet format whitespace --verify-no-changes` is
clean, and the build has 0 warnings.

## Related Issues

- Flaky test seen on #4753 after #4750 (FairRequestQueue without a
queue-wide lock)

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:48:18 +02:00
..