Files
UA-.NETStandard/coverage-thresholds.json
romanettandClaude Opus 5.5 bd38870cf7 perf: pump the server child's stdout on a dedicated thread (#4855)
## Summary

The 1-core client stall in the server load harness (`--client-cores 1`,
write->notify p99 0.7-1.7 s) was caused by the harness, not the OPC UA
client.

`StartServerChild` started `PumpServerOutput` with `Task.Run`. The pump
blocks in a synchronous `StreamReader.ReadLine` on the server's stdout
for the whole run, so it held one thread-pool worker permanently. With
`DOTNET_PROCESSOR_COUNT=1` the pool's minimum is one worker, so all
other work items waited for the pool's starvation injection. That is why
`DOTNET_ThreadPool_ForceMinWorkerThreads=8` and two processors both hid
the stall.

**Evidence:** 14 `dotnet-stack` samples of the constrained client during
the measurement window. In every sample, the only blocked pool worker
was in `ReadFile <- StreamReader.ReadLine <- Program.PumpServerOutput <-
StartServerChild`, and the other workers were idle. No `Opc.Ua` frame
was blocked.

**Fix:** run the pump on a dedicated background thread.

## Numbers

`run --scenario sub --sessions 25 --subs 20 --nodes 100 --pub 500
--write-interval 100 --duration 10 --warmup 6 --client-cores 1
--client-memory 512`, 5 alternating rounds (ms):

| round | base write->notify p99 | fix write->notify p99 | base write
p99 | fix write p99 |
|---|---|---|---|---|
| 1 | 659 | 61 | 619 | 12 |
| 2 | 885 | 310 | 859 | 15 |
| 3 | 306 | 303 | 52 | 23 |
| 4 | 1717 | 455 | 408 | 81 |
| 5 | 234 | 193 | 148 | 67 |

The baseline stalled (> 600 ms) in 3 of 5 runs and the fix in 0 of 5
(max 492 ms). The remaining ~300 ms p99 comes from the 500 ms publishing
interval.

## Test plan
- [x] Harness Release build (net10.0)
- [x] Alternating A/B runs above

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 14:46:35 +02:00

69 lines
3.2 KiB
JSON

{
"$comment": [
"Coverage gates for the UA .NET Standard stack. These are the ENFORCED rules;",
"see .azurepipelines/check-coverage.ps1 for the implementation and",
"docs/DeveloperGuide.md for the contributor-facing description.",
"Rates are percentages (0-100).",
"",
"codecov.io is still uploaded to, but only for reporting - its own",
"codecov/project and codecov/patch statuses are informational (see",
"codecov.yml) so that there is exactly one gate rather than two sets of",
"thresholds that can disagree about the same pull request.",
"",
"IMPORTANT: the coverage check is ADVISORY. The GitHub Actions 'code coverage' job",
"reports a clean failure when these thresholds are missed, so the miss is visible on",
"the pull request, but it does not belong in the branch ruleset. The required check is",
"'build-and-test summary', which only asserts that the tests themselves passed.",
"",
"'project' is an absolute floor over the whole merged coverage report.",
"Ratchet minimumLineRate/minimumBranchRate upward (together with baselineLineRate)",
"as coverage improves - never downward to make a red check go green.",
"",
"The initial values are seeded from the last codecov project reading (73.61%) with",
"headroom, because the in-pipeline number had not been observed yet when the gate",
"was introduced.",
"",
"The report is assembled from whichever legs of the run published Cobertura fragments,",
"so the number moves with the matrix: the pull-request run is the reference, and",
"scheduled runs read higher because the Debug / .NET 8 / .NET 9 / netstandard legs",
"contribute too. Re-calibrate against the pull-request figure.",
"",
"'patch' mirrors the old codecov/patch semantics: changed lines must reach",
"target percent, tolerating threshold percentage points. The requirement is",
"graduated by how much actually changed, because a percentage over a handful of",
"lines is noise: one uncovered line in a two-line fix reads as 50% and would fail",
"a flat floor, which teaches authors to ignore the check rather than fix it.",
"'bands' is consulted in order and the first band whose maxChangedLines covers the",
"patch wins; a band with enforced=false reports a warning instead of failing.",
"Anything larger than the last band falls through to target-threshold and is",
"always enforced, because at that size the percentage is meaningful and a big",
"untested change is exactly what the check exists to catch.",
"",
"'ignore' mirrors the exclusion list in codecov.yml so both report on the same",
"code - change the two together - and is applied to BOTH the project floor and",
"the changed-lines check."
],
"project": {
"minimumLineRate": 70.0,
"minimumBranchRate": 60.0,
"baselineLineRate": 73.6,
"advisoryDeltaTolerance": 1.0
},
"patch": {
"target": 80.0,
"threshold": 5.0,
"bands": [
{ "maxChangedLines": 10, "target": 50.0, "enforced": false },
{ "maxChangedLines": 100, "target": 60.0, "enforced": false }
]
},
"ignore": [
"tests/**",
"samples/**",
"perf/**",
"**/obj/**",
"**/bin/**",
"**/*.g.cs"
]
}