fix(modbus): batch event honors deadband — raise from the driver-filtered buffer
This commit is contained in:
@@ -53,6 +53,17 @@ public sealed class ModbusDriver
|
||||
// does a typed cast inside.
|
||||
private readonly ConcurrentDictionary<string, object> _lastPublishedByRef = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Per-cycle buffer of the items this driver actually PUBLISHED (i.e. that passed ShouldPublish),
|
||||
// keyed by subscription handle. The poll engine collects its own batch list unconditionally — it
|
||||
// has no knowledge of the deadband filter — so the batch event is raised from this buffer instead,
|
||||
// using the engine's onBatch purely as the end-of-cycle signal.
|
||||
//
|
||||
// No lock is needed on the per-handle List: the engine runs one loop task per subscription, so for
|
||||
// ONE handle all of a cycle's onChange calls and that cycle's single onBatch run sequentially on
|
||||
// that subscription's own task. Different subscriptions run concurrently — hence the per-handle
|
||||
// ConcurrentDictionary.
|
||||
private readonly ConcurrentDictionary<ISubscriptionHandle, List<DataChangeItem>> _cycleBatchItems = new();
|
||||
|
||||
// Last-written value per tag for the WriteOnChangeOnly suppression. Invalidated by reads
|
||||
// that return a different value (so an HMI-side change doesn't get masked).
|
||||
private readonly Dictionary<string, object?> _lastWrittenByRef = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -129,11 +140,25 @@ public sealed class ModbusDriver
|
||||
onChange: (handle, tagRef, snapshot) =>
|
||||
{
|
||||
if (!ShouldPublish(tagRef, snapshot)) return;
|
||||
// Buffer what we publish so the cycle-end batch carries exactly the same items.
|
||||
_cycleBatchItems.GetOrAdd(handle, _ => new List<DataChangeItem>())
|
||||
.Add(new DataChangeItem(tagRef, snapshot));
|
||||
OnDataChange?.Invoke(this, new DataChangeEventArgs(handle, tagRef, snapshot));
|
||||
},
|
||||
onError: HandlePollError,
|
||||
backoffCap: PollBackoffCap,
|
||||
onBatch: (handle, items) => OnDataChangeBatch?.Invoke(this, new DataChangeBatchEventArgs(handle, items)));
|
||||
// The engine's own items list is deliberately IGNORED: it is collected pre-deadband
|
||||
// (PollGroupEngine has no knowledge of ShouldPublish), so forwarding it would carry
|
||||
// changes the per-tag event suppressed — and a consumer that prefers the batch event
|
||||
// would see deadband silently stop working. The buffer holds exactly what the per-tag
|
||||
// event published, keeping the two events content-identical per the IBatchSubscribable
|
||||
// contract. onBatch is used only as the end-of-cycle signal. A cycle whose every change
|
||||
// was suppressed leaves the buffer empty and raises nothing.
|
||||
onBatch: (handle, _) =>
|
||||
{
|
||||
if (_cycleBatchItems.TryRemove(handle, out var published) && published.Count > 0)
|
||||
OnDataChangeBatch?.Invoke(this, new DataChangeBatchEventArgs(handle, published));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Upper bound on the poll-loop failure backoff — adopts the S7-proven 30 s cap fleet-wide (05/STAB-8).</summary>
|
||||
@@ -1215,6 +1240,9 @@ public sealed class ModbusDriver
|
||||
public Task UnsubscribeAsync(ISubscriptionHandle handle, CancellationToken cancellationToken)
|
||||
{
|
||||
_poll.Unsubscribe(handle);
|
||||
// Drop any partially-collected cycle buffer so a torn-down subscription leaks nothing.
|
||||
// Unsubscribe awaits the loop task, so no onChange can re-add after this point.
|
||||
_cycleBatchItems.TryRemove(handle, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1656,6 +1684,8 @@ public sealed class ModbusDriver
|
||||
lock (_autoProhibitedLock) _autoProhibited.Clear();
|
||||
|
||||
await _poll.DisposeAsync().ConfigureAwait(false);
|
||||
// After the engine's loops are drained, no onChange can re-populate the per-cycle buffers.
|
||||
_cycleBatchItems.Clear();
|
||||
|
||||
if (_transport is not null) await _transport.DisposeAsync().ConfigureAwait(false);
|
||||
_transport = null;
|
||||
|
||||
Reference in New Issue
Block a user