Files
UA-.NETStandard/codecov.yml
T
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

59 lines
1.9 KiB
YAML

# Codecov configuration
#
# Validate with:
# curl --data-binary @codecov.yml https://codecov.io/validate
#
# Codecov is REPORTING, not a gate. The enforced coverage rules live in
# coverage-thresholds.json and are applied by .azurepipelines/check-coverage.ps1
# inside the pipeline, where they can react to how much a patch actually
# changed. Codecov is kept for what it is genuinely better at: the pull-request
# comment, the file-by-file diff view and the coverage trend over time.
#
# Both status checks are therefore `informational: true`. They post a status so
# the number is visible, but they never fail and must never be added to the
# branch ruleset - two gates with two sets of thresholds would sooner or later
# disagree about the same pull request, and the one that is easier to silence
# would win. Flip `informational` to false only if you also retire the
# in-pipeline patch gate.
#
# The upload itself is optional and is skipped when the CODECOV_TOKEN secret
# is absent (as it is on fork pull requests). See `ENABLE_CODECOV` in
# .github/workflows/buildandtest.yml.
coverage:
precision: 2
round: down
status:
project:
default:
target: auto
threshold: 1%
informational: true
only_pulls: true
patch:
default:
target: 80%
threshold: 5%
informational: true
only_pulls: true
# Kept in step with the "ignore" list in coverage-thresholds.json so Codecov and
# the in-pipeline gate measure the same code. Change both together.
ignore:
- "tests/**"
- "samples/**"
- "perf/**"
- "**/obj/**"
- "**/bin/**"
- "**/*.g.cs"
# Each upload carries a flag (`actions`), and flags carry forward so a run that
# uploads fewer legs is not mistaken for a coverage swing.
flag_management:
default_rules:
carryforward: true
comment:
layout: "reach, diff, flags, files"
behavior: default
require_changes: false