9bb1363cd6
A tie cluster's truncated read left the width at one tick; once the gap above it was under two milliseconds nothing floored it, so segHi stepped down one tick per read: 30 rows at one ms 0.3 s below hi with a carried 1 s width took 10,006 identical reads (S8). (a) The top-anchored width is floored at the granularity, Min kept; (b) a segment no wider than the granularity is the unit that escalates to MaxTieClusterOverfetch and then refuses - per millisecond, exact for the ms-resolution Gateway (documented). Both are needed: the floor alone loops on crowded reads with a small tie budget. S8 is a repo test (4 shapes, reads <= 30, 0 repeated requests); random-data tests now measure the refusal unit as rows in any 1 ms window. Reviewer harness, 5,000 seeds x default/MSONLY/CROWD: worst page 70 reads (default/MSONLY), 63 (CROWD) at M=65536, p99.9 27-37; no page over 500 reads at any tie budget.
55 lines
3.5 KiB
C#
55 lines
3.5 KiB
C#
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
|
|
|
/// <summary>One walked-back reverse events page (<see cref="ReverseEventsReader"/>).</summary>
|
|
/// <param name="Events">At most the requested count, newest first by <c>(EventTimeUtc, EventId)</c>.</param>
|
|
/// <param name="Hint">What the next page of the same read resumes with.</param>
|
|
internal readonly record struct ReverseEventsPage(List<HistoricalEvent> Events, ReverseWalkHint Hint);
|
|
|
|
/// <summary>
|
|
/// Gitea #691.4: serves a Part 11 reverse events read — <c>(lo, hi]</c> newest-first — on a backend that reads events
|
|
/// forward only (<see cref="IHistorianDataSource.SupportsReverseEvents"/> = <c>false</c>: the Gateway) with the
|
|
/// <see cref="ReverseWalk"/>. A segment read is cut short when the source flags truncation (its continuation point); ties
|
|
/// are ordered by <see cref="EventIdTieOrder"/> because the gateway orders by event time only.
|
|
/// </summary>
|
|
internal static class ReverseEventsReader
|
|
{
|
|
/// <summary>Reads <c>(lo, hi]</c> newest-first from forward event reads.</summary>
|
|
/// <param name="source">A backend that reads events forward.</param>
|
|
/// <param name="sourceName">Event-source filter (null = all).</param>
|
|
/// <param name="hi">The later (inclusive) bound.</param>
|
|
/// <param name="lo">The earlier (exclusive) bound.</param>
|
|
/// <param name="want">How many events; <c>0</c> = all in the window (one forward read).</param>
|
|
/// <param name="maxTieCluster">The tie budget (<c>MaxTieClusterOverfetch</c>) — a ceiling only.</param>
|
|
/// <param name="nowUtc">Now, for the look-ahead clamp.</param>
|
|
/// <param name="hint">What the previous page carried (default = none).</param>
|
|
/// <param name="ct">Cancellation.</param>
|
|
/// <returns>The events, newest first, and the hint for the next page.</returns>
|
|
/// <exception cref="HistoryReadLimitExceededException">One timestamp holds more than the tie budget.</exception>
|
|
public static async Task<ReverseEventsPage> ReadAsync(
|
|
IHistorianDataSource source, string? sourceName, DateTime hi, DateTime lo, uint want, int maxTieCluster,
|
|
DateTime nowUtc, ReverseWalkHint hint, CancellationToken ct)
|
|
{
|
|
var budget = Math.Max(1, maxTieCluster);
|
|
var (rows, next) = await ReverseWalk.WalkAsync<HistoricalEvent>(
|
|
async (start, end, cap, token) =>
|
|
{
|
|
// cap 0 = the whole window: int.MaxValue is the events contract's "no cap" (a non-positive value would
|
|
// mean the backend's DEFAULT cap).
|
|
var r = await source.ReadEventsAsync(sourceName, start, end, cap == 0 ? int.MaxValue : cap, reverse: false, token)
|
|
.ConfigureAwait(false);
|
|
return new ForwardSegmentRead<HistoricalEvent>(r.Events, r.ContinuationPoint is not null);
|
|
},
|
|
ev => ev.EventTimeUtc.Ticks, EventIdTieOrder.Instance, hi, lo, want, budget, source.TimestampResolutionTicks,
|
|
nowUtc, hint,
|
|
at => new HistoryReadLimitExceededException(budget,
|
|
$"A reverse events read of '{sourceName}' at {at:O} found more than {budget} events within one millisecond; the " +
|
|
"backend reads forward only, so the latest of them cannot be selected. Increase " +
|
|
"ServerHistorian:MaxTieClusterOverfetch."),
|
|
ct)
|
|
.ConfigureAwait(false);
|
|
return new ReverseEventsPage(rows, next);
|
|
}
|
|
}
|