mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-25 20:00:31 +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>
59 lines
1.9 KiB
YAML
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
|