mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-25 03:29:06 +08:00
## 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>
69 lines
3.2 KiB
JSON
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"
|
|
]
|
|
}
|