Merge branch 'fix/archreview-p2' into main (P2 tier: completeness & polish)
# Conflicts: # archreview/remediation/00-tracking.md # clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayCliSecretRedactor.cs # clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs # src/ZB.MOM.WW.MxGateway.Worker.Tests/Ipc/WorkerFrameProtocolTests.cs
This commit is contained in:
@@ -8,6 +8,17 @@ namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
||||
/// <summary>
|
||||
/// Thread-safe queue for MxAccess events with capacity overflow and fault tracking.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ownership invariant: <see cref="Enqueue"/> takes ownership of the
|
||||
/// <see cref="MxEvent"/> passed to it — it stamps the worker sequence and
|
||||
/// timestamp on that same instance and enqueues it directly, without
|
||||
/// cloning (WRK-11). Every caller must therefore pass a freshly built
|
||||
/// <see cref="MxEvent"/> that it does not retain, reuse, or mutate after the
|
||||
/// call returns. All production callers (MxAccessBaseEventSink,
|
||||
/// MxAccessAlarmEventSink, AlarmCommandHandler) build a new event per
|
||||
/// Enqueue via the mapper and satisfy this; the value cache stores its own
|
||||
/// independent snapshot (see <see cref="MxAccessValueCache.Set"/>).
|
||||
/// </remarks>
|
||||
public sealed class MxAccessEventQueue
|
||||
{
|
||||
/// <summary>
|
||||
@@ -110,8 +121,11 @@ public sealed class MxAccessEventQueue
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an MxAccess event, assigning a sequence number and timestamp.
|
||||
/// Takes ownership of <paramref name="mxEvent"/>: the sequence and timestamp
|
||||
/// are stamped on that same instance and it is enqueued without cloning, so
|
||||
/// the caller must pass a freshly built event it does not reuse afterwards.
|
||||
/// </summary>
|
||||
/// <param name="mxEvent">MXAccess event to enqueue.</param>
|
||||
/// <param name="mxEvent">Freshly built MXAccess event to enqueue; ownership transfers to the queue.</param>
|
||||
public void Enqueue(MxEvent mxEvent)
|
||||
{
|
||||
if (mxEvent is null)
|
||||
@@ -132,13 +146,17 @@ public sealed class MxAccessEventQueue
|
||||
throw new MxAccessEventQueueOverflowException(capacity);
|
||||
}
|
||||
|
||||
MxEvent queuedEvent = mxEvent.Clone();
|
||||
queuedEvent.WorkerSequence = ++lastEventSequence;
|
||||
queuedEvent.WorkerTimestamp = Timestamp.FromDateTime(DateTime.UtcNow);
|
||||
// WRK-11: stamp the sequence/timestamp on the caller's own event and
|
||||
// enqueue that same instance under the lock instead of cloning. See
|
||||
// the ownership invariant on the class summary — the caller hands the
|
||||
// event over exclusively, so a defensive Clone() here is pure
|
||||
// overhead on the hottest path.
|
||||
mxEvent.WorkerSequence = ++lastEventSequence;
|
||||
mxEvent.WorkerTimestamp = Timestamp.FromDateTime(DateTime.UtcNow);
|
||||
|
||||
WorkerEvent workerEvent = new()
|
||||
{
|
||||
Event = queuedEvent,
|
||||
Event = mxEvent,
|
||||
};
|
||||
events.Enqueue(workerEvent);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,20 @@ public sealed class MxAccessValueCache
|
||||
throw new ArgumentNullException(nameof(mxEvent));
|
||||
}
|
||||
|
||||
// WRK-11: the event sink no longer clones before enqueue, so the passed
|
||||
// mxEvent is the very instance handed to the outbound queue. Deep-copy
|
||||
// the value/timestamp/statuses payload we retain here so the cache's
|
||||
// snapshot stays independent of the enqueued (and later serialized)
|
||||
// event — the two must never share mutable protobuf sub-messages.
|
||||
// Value is always set for OnDataChange; SourceTimestamp may be unset when
|
||||
// the source timestamp could not be parsed, so both are cloned only when
|
||||
// present. The null-forgiving result matches CachedValue's non-null-
|
||||
// annotated parameters, which already accepted a runtime-null value or
|
||||
// timestamp before WRK-11 (the ternary keeps the compiler's null-state
|
||||
// from poisoning to maybe-null, which a plain null check would do).
|
||||
MxValue cachedValue = mxEvent.Value is null ? null! : mxEvent.Value.Clone();
|
||||
Timestamp cachedTimestamp = mxEvent.SourceTimestamp is null ? null! : mxEvent.SourceTimestamp.Clone();
|
||||
|
||||
long key = CreateItemKey(serverHandle, itemHandle);
|
||||
lock (syncRoot)
|
||||
{
|
||||
@@ -49,10 +63,10 @@ public sealed class MxAccessValueCache
|
||||
|
||||
entries[key] = new CachedValue(
|
||||
nextVersion,
|
||||
mxEvent.Value,
|
||||
cachedValue,
|
||||
mxEvent.Quality,
|
||||
mxEvent.SourceTimestamp,
|
||||
mxEvent.Statuses);
|
||||
cachedTimestamp,
|
||||
mxEvent.Statuses.Clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user