fix(opcua): reverse HistoryRead-Raw paging no longer stops after the first page
A reverse read (start > end) with a small NumValuesPerNode resumed through the tie-cluster path, which drained the cluster and stepped FORWARD to T + 1 tick - outside a reverse window - so the read ended with GoodNoData after one sample. SliceTieCluster now steps down to T - 1 tick for a reverse read (terminal at the excluded end), and the node manager slices the over-fetched cluster in the read's own direction so the skip count is not applied from the wrong end. Forward paging is unchanged. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -5898,10 +5898,16 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
// Compute in uint so +1 can never wrap: Math.Max(1,…) also guards against a zero/negative
|
||||
// config value slipping through (Validate() warns but does not block startup).
|
||||
var overfetchCap = (uint)Math.Max(1, MaxTieClusterOverfetch) + 1u;
|
||||
var cluster = (await HistorianDataSource
|
||||
IReadOnlyList<DataValueSnapshot> cluster = (await HistorianDataSource
|
||||
.ReadRawAsync(tagname, startUtc, startUtc, overfetchCap, ct)
|
||||
.ConfigureAwait(false)).Samples;
|
||||
|
||||
// A start == end point read returns the ties in the backend's FORWARD order, but a reverse read
|
||||
// emitted them (and counted boundarySkip) in descending order — the mirror of forward. Slice the
|
||||
// cluster in the read's own direction, or the skip would index from the wrong end and re-emit ties.
|
||||
var reverse = startUtc > endUtc;
|
||||
if (reverse) cluster = cluster.Reverse().ToList();
|
||||
|
||||
// Absurd burst: more ties than we're willing to buffer in memory. Preserve today's loud-fail
|
||||
// for that node rather than over-fetch an unbounded cluster; the operator's remedy is a
|
||||
// larger ServerHistorian:MaxTieClusterOverfetch (or NumValuesPerNode).
|
||||
@@ -5916,9 +5922,12 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
return;
|
||||
}
|
||||
|
||||
// Direction comes from the window itself (Part 11: start > end is a reverse read). A resumed reverse
|
||||
// read keeps start > end, because a reverse resume point is always strictly after endUtc.
|
||||
HistoryPaging.SliceTieCluster(
|
||||
cluster.Count, boundarySkip, numValuesPerNode, startUtc, endUtc,
|
||||
out var sliceStart, out var sliceCount, out var nextStartUtc, out var nextSkip);
|
||||
out var sliceStart, out var sliceCount, out var nextStartUtc, out var nextSkip,
|
||||
reverse: reverse);
|
||||
|
||||
var slice = new List<DataValueSnapshot>(sliceCount);
|
||||
for (var i = sliceStart; i < sliceStart + sliceCount; i++) slice.Add(cluster[i]);
|
||||
|
||||
Reference in New Issue
Block a user