[opcuaclient] OpcUaClient — Honor server OperationLimits #333

Merged
dohertj2 merged 1 commits from auto/opcuaclient/3 into auto/driver-gaps 2026-04-25 15:41:19 -04:00
Owner

Summary

Honors the upstream server's OperationLimits so batch Read/Write calls don't trigger BadTooManyOperations on capacity-constrained UA servers.

  • New OperationLimitsCache record (4 nullable uints — null/0 = no limit).
  • Private EnsureOperationLimitsFetchedAsync lazily calls Session.FetchOperationLimitsAsync once on the first batch op. Normalizes the spec sentinel 0null.
  • Static ChunkBy<T>(IReadOnlyList<T>, uint?) helper yields ArraySegment<T> slices. null/0/oversize cap → single slice (unchanged single SDK call).
  • Wired into ReadAsync (MaxNodesPerRead) and WriteAsync (MaxNodesPerWrite). Per-chunk indexMap keeps result-index alignment correct across slices.
  • Cache is reset in OnReconnectComplete (so capability changes across a reconnect window are picked up) and in ShutdownAsync.
  • Browse and HistoryRead are single-node calls today, so chunking doesn't apply at the current driver shape — caps remain available in the cache for when those paths grow multi-node batches.

Test plan

  • dotnet build src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient — clean (0 / 0)
  • dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests104 / 104 passed (9 new in OpcUaClientOperationLimitsTests: 12-tag/cap-5 = 5+5+2, null-cap and zero-cap no-chunk, cap≥input, cap=1, exact match, empty input, cache record shape, pre-init null state)
  • Integration tests — skipped (live UA server required)

🤖 Auto-generated by the Mode-B execution loop. Closes #275.

Closes #275

## Summary Honors the upstream server's `OperationLimits` so batch Read/Write calls don't trigger `BadTooManyOperations` on capacity-constrained UA servers. - New `OperationLimitsCache` record (4 nullable uints — null/0 = no limit). - Private `EnsureOperationLimitsFetchedAsync` lazily calls `Session.FetchOperationLimitsAsync` once on the first batch op. Normalizes the spec sentinel `0` → `null`. - Static `ChunkBy<T>(IReadOnlyList<T>, uint?)` helper yields `ArraySegment<T>` slices. null/0/oversize cap → single slice (unchanged single SDK call). - Wired into `ReadAsync` (`MaxNodesPerRead`) and `WriteAsync` (`MaxNodesPerWrite`). Per-chunk `indexMap` keeps result-index alignment correct across slices. - Cache is reset in `OnReconnectComplete` (so capability changes across a reconnect window are picked up) and in `ShutdownAsync`. - Browse and HistoryRead are single-node calls today, so chunking doesn't apply at the current driver shape — caps remain available in the cache for when those paths grow multi-node batches. ## Test plan - [x] `dotnet build src/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient` — clean (0 / 0) - [x] `dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests` — **104 / 104 passed** (9 new in `OpcUaClientOperationLimitsTests`: 12-tag/cap-5 = 5+5+2, null-cap and zero-cap no-chunk, cap≥input, cap=1, exact match, empty input, cache record shape, pre-init null state) - [ ] Integration tests — skipped (live UA server required) 🤖 Auto-generated by the Mode-B execution loop. Closes #275. Closes #275
dohertj2 added 1 commit 2026-04-25 15:41:15 -04:00
dohertj2 merged commit a04ba2af7a into auto/driver-gaps 2026-04-25 15:41:19 -04:00
dohertj2 deleted branch auto/opcuaclient/3 2026-04-25 15:41:19 -04:00
Sign in to join this conversation.