fix(runtime): harden batch forwarding — detach pins, malformed-raise guard, struct items
This commit is contained in:
@@ -60,7 +60,9 @@ public sealed record DataChangeItem(string FullReference, DataValueSnapshot Snap
|
||||
/// <summary>Event payload for <see cref="IBatchSubscribable.OnDataChangeBatch"/> — every attribute
|
||||
/// that changed within ONE production burst (a poll cycle, an inbound message, a stream chunk).</summary>
|
||||
/// <param name="SubscriptionHandle">The handle returned by the original <see cref="ISubscribable.SubscribeAsync"/> call.</param>
|
||||
/// <param name="Items">The changed attributes, one entry per changed tag. Never empty.</param>
|
||||
/// <param name="Items">The changed attributes, one entry per changed tag. Never empty. Ownership transfers
|
||||
/// to the consumer, which may retain the list past the raise (it is marshalled onto another thread) — so the
|
||||
/// driver MUST NOT mutate or reuse the list instance after raising; allocate a fresh one per burst.</param>
|
||||
public sealed record DataChangeBatchEventArgs(
|
||||
ISubscriptionHandle SubscriptionHandle,
|
||||
IReadOnlyList<DataChangeItem> Items);
|
||||
|
||||
@@ -109,8 +109,11 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
/// (#519) — not a severity bucket. Classify it with <see cref="OpcUaStatus.IsGood"/> and friends rather
|
||||
/// than comparing it to <see cref="OpcUaStatus.Good"/>.</para></summary>
|
||||
public sealed record AttributeValuePublished(string DriverInstanceId, string FullReference, object? Value, uint StatusCode, DateTime TimestampUtc);
|
||||
/// <summary>One value within an <see cref="AttributeValueBatchPublished"/>.</summary>
|
||||
public sealed record AttributeValueItem(string FullReference, object? Value, uint StatusCode, DateTime TimestampUtc);
|
||||
/// <summary>One value within an <see cref="AttributeValueBatchPublished"/>.
|
||||
/// <para>A struct deliberately: an <c>AttributeValueItem[]</c> then holds its items inline, so a burst of
|
||||
/// N values costs one array allocation instead of one array plus N boxes. Indexed access through
|
||||
/// <see cref="IReadOnlyList{T}"/> does not box.</para></summary>
|
||||
public readonly record struct AttributeValueItem(string FullReference, object? Value, uint StatusCode, DateTime TimestampUtc);
|
||||
/// <summary>Batched form of <see cref="AttributeValuePublished"/> — every changed attribute of ONE
|
||||
/// driver production burst in one message. The parent fans the whole batch to the mux and the OPC UA
|
||||
/// publish path in one hop each. Status codes ride verbatim (#519).</summary>
|
||||
@@ -900,7 +903,13 @@ public sealed class DriverInstanceActor : ReceiveActor, IWithTimers
|
||||
// Tell, one parent hop, one mux pass per burst instead of per tag.
|
||||
if (subscribable is IBatchSubscribable batchable)
|
||||
{
|
||||
_batchChangeHandler = (_, args) => self.Tell(new DataChangeBatchForward(args.Items));
|
||||
_batchChangeHandler = (_, args) =>
|
||||
{
|
||||
// A null/empty raise from a buggy driver must never enter the mailbox: an unguarded null
|
||||
// would throw in the handler, restart the actor, and tear down the driver transport.
|
||||
if (args?.Items is not { Count: > 0 }) return;
|
||||
self.Tell(new DataChangeBatchForward(args.Items));
|
||||
};
|
||||
batchable.OnDataChangeBatch += _batchChangeHandler;
|
||||
}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user