feat(drivers): per-driver poll-overrun meter on the shared OtOpcUa Meter; NoteOverrun + backoffCap doc notes
This commit is contained in:
@@ -670,6 +670,50 @@ public sealed class PollGroupEngineTests
|
||||
overruns.Count.ShouldBeLessThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The counter and the callback must be the SAME signal, not two approximations of it: every
|
||||
/// increment of <see cref="PollGroupEngine.OverrunCount"/> is matched by exactly one
|
||||
/// <c>onOverrun</c> invocation. This is what lets a driver's meter (the production consumer,
|
||||
/// <c>OtOpcUaTelemetry.PollOverruns</c>) be read as the counter itself — a callback that
|
||||
/// fired on only some increments, or twice on one, would silently mis-scale the metric.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The two existing pacing tests
|
||||
/// (<see cref="A_slow_reader_overrun_is_counted_and_the_next_poll_starts_promptly"/> /
|
||||
/// <see cref="A_fast_reader_never_overruns"/>) assert each side against its own loose
|
||||
/// threshold, so they would both still pass if the callback fired on, say, every other
|
||||
/// increment. This one compares the two directly, and does so only AFTER
|
||||
/// <c>UnsubscribeAsync</c> has drained the loop — while a poll is in flight the increment
|
||||
/// and the callback are two separate statements, so a live read could legitimately catch
|
||||
/// them one apart and the equality would be flaky rather than falsifiable.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task Every_overrun_increment_is_matched_by_exactly_one_onOverrun_callback()
|
||||
{
|
||||
async Task<IReadOnlyList<DataValueSnapshot>> Reader(IReadOnlyList<string> refs, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay(120, ct);
|
||||
var now = DateTime.UtcNow;
|
||||
// A fresh value every poll, so the change path runs on every cycle too.
|
||||
return refs.Select(_ => new DataValueSnapshot(now.Ticks, 0u, now, now)).ToList();
|
||||
}
|
||||
|
||||
var overruns = new ConcurrentQueue<TimeSpan>();
|
||||
await using var engine = new PollGroupEngine(
|
||||
Reader,
|
||||
(_, _, _) => { },
|
||||
minInterval: TimeSpan.FromMilliseconds(20),
|
||||
onOverrun: e => overruns.Enqueue(e));
|
||||
|
||||
var handle = engine.Subscribe(["X"], TimeSpan.FromMilliseconds(40));
|
||||
await Task.Delay(1000, TestContext.Current.CancellationToken);
|
||||
await engine.UnsubscribeAsync(handle);
|
||||
|
||||
// Sanity: the scenario actually produced overruns, so the equality below is not vacuous.
|
||||
engine.OverrunCount.ShouldBeGreaterThanOrEqualTo(3);
|
||||
overruns.Count.ShouldBe((int)engine.OverrunCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The async teardown must be behaviourally identical to the sync one: the handle is known,
|
||||
/// the loop stops, and no further callback fires. Mirrors
|
||||
|
||||
Reference in New Issue
Block a user