docs: strip task-tracking IDs from code comments and fill missing XML docs
Tracking IDs are review bookkeeping that rots in shipped source; the rationale they tagged is kept. Comment-only change (CommentChecker 458 -> 16 false positives).
This commit is contained in:
@@ -263,6 +263,11 @@ public sealed class MxStatusProxyConverter
|
||||
/// </summary>
|
||||
private readonly struct StatusFields
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="StatusFields"/> struct.</summary>
|
||||
/// <param name="success">Accessor for the raw success member.</param>
|
||||
/// <param name="category">Accessor for the raw category member.</param>
|
||||
/// <param name="detectedBy">Accessor for the raw detected-by member.</param>
|
||||
/// <param name="detail">Accessor for the raw detail member.</param>
|
||||
public StatusFields(
|
||||
Func<object, int> success,
|
||||
Func<object, int> category,
|
||||
@@ -275,12 +280,16 @@ public sealed class MxStatusProxyConverter
|
||||
Detail = detail;
|
||||
}
|
||||
|
||||
/// <summary>Gets the accessor for the raw success member.</summary>
|
||||
public Func<object, int> Success { get; }
|
||||
|
||||
/// <summary>Gets the accessor for the raw category member.</summary>
|
||||
public Func<object, int> Category { get; }
|
||||
|
||||
/// <summary>Gets the accessor for the raw detected-by member.</summary>
|
||||
public Func<object, int> DetectedBy { get; }
|
||||
|
||||
/// <summary>Gets the accessor for the raw detail member.</summary>
|
||||
public Func<object, int> Detail { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed class WorkerFrameReader
|
||||
private readonly WorkerFrameProtocolOptions _options;
|
||||
private readonly Stream _stream;
|
||||
|
||||
// Reused across frames rather than allocated per read (PERF-22). Safe because ReadAsync is
|
||||
// Reused across frames rather than allocated per read. Safe because ReadAsync is
|
||||
// single-consumer by construction; the prefix is fully overwritten by every read.
|
||||
//
|
||||
// The single-consumer invariant has to survive teardown as well as steady state, because a read
|
||||
|
||||
@@ -103,13 +103,13 @@ public sealed class WorkerFrameWriter
|
||||
/// <param name="cancellationToken">Token to cancel waiting for the write lock.</param>
|
||||
/// <returns>A task that completes when the frame has been written and flushed.</returns>
|
||||
/// <remarks>
|
||||
/// Cancellation contract (WRK-22): if the token fires while this call is waiting for the write
|
||||
/// Cancellation contract: if the token fires while this call is waiting for the write
|
||||
/// lock, the frame is tombstoned so it is never written — unless a draining lock-holder has
|
||||
/// already claimed it, in which case the frame may still reach the wire even though this call
|
||||
/// observes <see cref="OperationCanceledException"/>. That residual window is by design: blocking
|
||||
/// the canceller behind the very write it is abandoning would defeat the point of cancellation.
|
||||
/// The abandoned frame's completion gets a fault-observing continuation so a write failure after
|
||||
/// the caller unwinds never raises an unobserved-task exception (NEXT-04).
|
||||
/// the caller unwinds never raises an unobserved-task exception.
|
||||
/// <para>
|
||||
/// Latency contract: a control frame's bytes are written, flushed, and its completion
|
||||
/// resolved before the events a drain pass writes after it — the delivery point of a heartbeat,
|
||||
@@ -201,7 +201,7 @@ public sealed class WorkerFrameWriter
|
||||
/// <summary>
|
||||
/// Queues a whole batch of envelopes at one priority under a single lock acquisition and drains
|
||||
/// it, so a burst of frames — the event drain loop's hot path — pays one flush for the batch
|
||||
/// rather than one per frame (WRK-25, realizing the WRK-12 coalescing on the path it was built
|
||||
/// rather than one per frame (realizing the flush coalescing on the path it was built
|
||||
/// for). Intra-batch order is preserved because the enqueue is atomic under <c>_gate</c> and each
|
||||
/// class queue is FIFO; the control-before-event guarantee still holds because any concurrently
|
||||
/// queued control frame is drained ahead of this batch by <see cref="DequeueNext"/>, and — since
|
||||
@@ -221,10 +221,10 @@ public sealed class WorkerFrameWriter
|
||||
/// A per-frame rejection inside the batch (for example one oversized event) surfaces from the
|
||||
/// awaited completions as its <see cref="WorkerFrameProtocolException"/>; the remaining frames are
|
||||
/// still observed so none faults unobserved. Cancellation while waiting for the lock tombstones
|
||||
/// every still-unclaimed frame in the batch, per the WRK-22 contract on
|
||||
/// every still-unclaimed frame in the batch, per the cancellation contract on
|
||||
/// <see cref="WriteAsync(WorkerEnvelope, WorkerFrameWritePriority, CancellationToken)"/>; frames
|
||||
/// the cancelled caller abandons (claimed mid-write, or already faulted) get a fault-observing
|
||||
/// continuation so a later write failure never raises an unobserved-task exception (NEXT-04).
|
||||
/// continuation so a later write failure never raises an unobserved-task exception.
|
||||
/// <para>
|
||||
/// This path deliberately keeps the plain wait-then-drain shape rather than the single-frame
|
||||
/// path's completion-versus-lock race. A batch caller's result is its whole set of completions,
|
||||
@@ -338,7 +338,7 @@ public sealed class WorkerFrameWriter
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Observes any fault on a frame the cancelled caller stops awaiting (NEXT-04). A frame
|
||||
/// Observes any fault on a frame the cancelled caller stops awaiting. A frame
|
||||
/// claimed by a draining lock-holder — or already faulted by a concurrent
|
||||
/// <c>FailAllQueued</c> — completes on a task nobody awaits after cancellation unwinds the
|
||||
/// caller; a later write failure would then surface as an unobserved-task exception. A
|
||||
@@ -353,7 +353,7 @@ public sealed class WorkerFrameWriter
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the NEXT-04 fault-observing continuation to a task this writer starts and then
|
||||
/// Attaches the fault-observing continuation to a task this writer starts and then
|
||||
/// discards. Every such task must carry one: with no awaiter, a fault would otherwise reach
|
||||
/// nobody and resurface as <see cref="TaskScheduler.UnobservedTaskException"/> at finalization
|
||||
/// — a detached, unattributable failure long after the code that caused it. The continuation
|
||||
@@ -388,7 +388,7 @@ public sealed class WorkerFrameWriter
|
||||
/// <param name="lockWait">Outstanding write-lock acquisition the caller has walked away from.</param>
|
||||
private void DetachLockWait(Task lockWait)
|
||||
{
|
||||
// The continuation task is discarded, so it takes the NEXT-04 fault observer: nothing awaits
|
||||
// The continuation task is discarded, so it takes the fault observer: nothing awaits
|
||||
// it, and a throw out of OnDetachedLockWaitSettled would otherwise be an unobserved-task
|
||||
// exception raised at finalization rather than an attributable failure here.
|
||||
ObserveFault(lockWait.ContinueWith(
|
||||
@@ -418,7 +418,7 @@ public sealed class WorkerFrameWriter
|
||||
return;
|
||||
}
|
||||
|
||||
// Discarded, so it takes the NEXT-04 fault observer too. DrainDetachedAsync swallows the drain
|
||||
// Discarded, so it takes the fault observer too. DrainDetachedAsync swallows the drain
|
||||
// itself, but its release sits in a finally outside that catch: a Release that ever throws (a
|
||||
// SemaphoreFullException from some future double-release regression, say) must fail somewhere
|
||||
// attributable rather than at finalization.
|
||||
@@ -455,9 +455,8 @@ public sealed class WorkerFrameWriter
|
||||
// The stream write itself is not cancellable: a frame is written atomically or fails, never left
|
||||
// half-written on the pipe because a caller gave up waiting.
|
||||
//
|
||||
// Flushes are coalesced within a priority class rather than blindly across the whole pass (WRK-12 /
|
||||
// IPC-15, narrowed by the control-frame completion decoupling): each frame is written to the stream
|
||||
// but not flushed individually, and one FlushAsync runs at the end of the pass — plus one at each
|
||||
// Flushes are coalesced within a priority class rather than blindly across the whole pass (narrowed by the control-frame completion
|
||||
// decoupling): each frame is written to the stream but not flushed individually, and one FlushAsync runs at the end of the pass — plus one at each
|
||||
// control-to-event boundary, which flushes and completes the control frames written so far before
|
||||
// the event backlog behind them is written, instead of after it. Without that boundary flush the
|
||||
// priority scheduler only got control *bytes* out early: their delivery point, and every waiting
|
||||
@@ -468,7 +467,7 @@ public sealed class WorkerFrameWriter
|
||||
// pass. Cost is bounded: a pure-event pass (the event hot path) still pays exactly one flush, a burst
|
||||
// of control frames still pays one for the whole burst, and only a pass that actually mixes both
|
||||
// classes pays a second — never one flush per control frame, which is the syscall-per-heartbeat cost
|
||||
// WRK-12 removed.
|
||||
// the earlier per-frame flush removed.
|
||||
private async Task DrainQueuedFramesAsync()
|
||||
{
|
||||
List<PendingFrame> written = new List<PendingFrame>();
|
||||
@@ -582,7 +581,7 @@ public sealed class WorkerFrameWriter
|
||||
}
|
||||
|
||||
// Returns the next frame to write, control frames first, skipping any frame a cancelled caller
|
||||
// tombstoned while it waited for the lock (WRK-22). The frame actually returned is marked Claimed
|
||||
// tombstoned while it waited for the lock. The frame actually returned is marked Claimed
|
||||
// under _gate in the same critical section that checks the tombstone, so a claim and a concurrent
|
||||
// cancel are mutually exclusive: whichever acquires _gate first wins.
|
||||
private PendingFrame? DequeueNext()
|
||||
@@ -640,7 +639,7 @@ public sealed class WorkerFrameWriter
|
||||
// Stamp the sequence at the actual point of writing, under the write lock, so the wire order
|
||||
// and the stamped sequence agree regardless of caller concurrency or priority.
|
||||
//
|
||||
// Peek-stamp-commit (WRK-23): the sequence participates in CalculateSize() (varint width),
|
||||
// Peek-stamp-commit: the sequence participates in CalculateSize() (varint width),
|
||||
// so it must be stamped before the size checks — but a per-frame rejection must not burn a
|
||||
// number, or the wire shows phantom gaps that an operator reads as lost frames. Stamp a
|
||||
// candidate, validate the stamped envelope, and commit the counter only once the frame is
|
||||
@@ -668,7 +667,7 @@ public sealed class WorkerFrameWriter
|
||||
// Serialize once into a single pooled buffer that carries the 4-byte length prefix followed
|
||||
// by the payload, then issue one stream write. This avoids a second serialization pass, a
|
||||
// separate prefix array, a separate prefix write, and any per-frame heap allocation — the
|
||||
// gateway-side writer's pooled-buffer shape (PERF-22), now matched on the net48 side. The
|
||||
// gateway-side writer's pooled-buffer shape, now matched on the net48 side. The
|
||||
// rented buffer may be larger than requested, so only the first frameLength bytes are ever
|
||||
// written. The buffer is returned only after this frame's write has completed; the batch
|
||||
// flush deferred to the end of the drain (see DrainQueuedFramesAsync) does not read from it.
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class WorkerPipeSession
|
||||
|
||||
// The transport the reader and writer share, when this session was handed one to own. Null for
|
||||
// the reader/writer constructors, whose callers keep ownership of whatever streams they built the
|
||||
// pair over. Owning it is what lets teardown close the handle (WRK-31): on net48 a pipe read
|
||||
// pair over. Owning it is what lets teardown close the handle: on net48 a pipe read
|
||||
// parked in the kernel cannot be cancelled, only unblocked by disposal.
|
||||
private readonly Stream? _transportStream;
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class WorkerPipeSession
|
||||
|
||||
// Closing the transport is what actually ends a pipe read parked in the kernel: on net48
|
||||
// NamedPipeClientStream.ReadAsync ignores its CancellationToken, so the message loop's
|
||||
// cancellation can never reach one (WRK-31). It is deliberately the LAST teardown step,
|
||||
// cancellation can never reach one. It is deliberately the LAST teardown step,
|
||||
// because in the ordinary case every frame this session will ever write has been written
|
||||
// AND flushed by the time control reaches here: WorkerFrameWriter.WriteAsync signals only
|
||||
// after both, and each exit path awaits its final write before unwinding — the shutdown
|
||||
@@ -563,7 +563,7 @@ public sealed class WorkerPipeSession
|
||||
/// task eventually faults, so a task nobody is awaiting any more can never reach the
|
||||
/// finalizer with an unobserved exception. Mirrors the shape
|
||||
/// <c>WorkerFrameWriter.ObserveAbandonedFault</c> uses for frames a cancelled caller stops
|
||||
/// awaiting (NEXT-04). Faulting is the only outcome that runs the continuation, and the
|
||||
/// awaiting. Faulting is the only outcome that runs the continuation, and the
|
||||
/// continuation is scheduled inline, so this costs nothing on the ordinary path.
|
||||
/// </summary>
|
||||
/// <param name="task">Task that may fault after its awaiter has walked away.</param>
|
||||
@@ -614,7 +614,7 @@ public sealed class WorkerPipeSession
|
||||
}
|
||||
|
||||
// Submit the whole drained batch through the writer's batch entry point under one lock
|
||||
// acquisition so the burst pays a single flush instead of one per event (WRK-25). Events
|
||||
// acquisition so the burst pays a single flush instead of one per event. Events
|
||||
// are the low-priority frame class: the writer holds them behind any pending control frame
|
||||
// (reply, fault, heartbeat, shutdown ack) so those are not delayed behind an event backlog,
|
||||
// and intra-batch order is preserved.
|
||||
@@ -634,7 +634,7 @@ public sealed class WorkerPipeSession
|
||||
when (exception.ErrorCode == WorkerFrameProtocolErrorCode.MessageTooLarge)
|
||||
{
|
||||
// A single oversized event surfaces from the batch's awaited completions; the death is
|
||||
// still IPC-30's structured, event-naming fault. Map the rejection back to the first
|
||||
// still the structured, event-naming fault. Map the rejection back to the first
|
||||
// event in batch order whose envelope overshoots the negotiated maximum — the same
|
||||
// frame the writer rejected first.
|
||||
await FaultOnOversizedEventAsync(
|
||||
@@ -665,8 +665,8 @@ public sealed class WorkerPipeSession
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the session on an event that cannot be framed, but deliberately and diagnosably
|
||||
/// (IPC-30). An event above the negotiated frame maximum is undeliverable end to end — the
|
||||
/// Ends the session on an event that cannot be framed, but deliberately and diagnosably.
|
||||
/// An event above the negotiated frame maximum is undeliverable end to end — the
|
||||
/// pipe maximum sits only an envelope reserve above the public gRPC cap — so dropping it
|
||||
/// would silently make the event stream unfaithful, and synthesizing a placeholder is barred
|
||||
/// by the no-synthesized-events rule. Instead the worker records which event blocked (never
|
||||
@@ -869,7 +869,7 @@ public sealed class WorkerPipeSession
|
||||
/// overshot the frame maximum. The fallback itself is a handful of bytes, so it fits any
|
||||
/// sane negotiated maximum; the only way it can also throw <c>MessageTooLarge</c> is a
|
||||
/// pathologically tiny negotiated maximum below the gateway's validation floor — the
|
||||
/// pre-existing WRK-24 gap, which adds the negotiated-max lower bound that makes this
|
||||
/// pre-existing gap, which adds the negotiated-max lower bound that makes this
|
||||
/// unreachable. Until then, a defensive swallow keeps the "no diagnostics command is
|
||||
/// session-fatal" invariant true even in that degenerate config: the correlation goes
|
||||
/// unanswered and the gateway's own per-command timeout covers it, but the session lives.
|
||||
@@ -1028,7 +1028,7 @@ public sealed class WorkerPipeSession
|
||||
/// <c>MxCommandReply</c> does not stop at the pipe: the gateway returns it verbatim through
|
||||
/// <c>Invoke</c>, where the public gRPC send cap applies, and the gateway's validator
|
||||
/// couples the two limits so the pipe maximum never exceeds the gRPC cap by more than two
|
||||
/// reserves (IPC-33). Subtracting both therefore keeps the packed events at or below the
|
||||
/// reserves. Subtracting both therefore keeps the packed events at or below the
|
||||
/// gRPC cap and leaves the wrapper — correlation id, protocol status, kind, the
|
||||
/// <c>drain_events</c> length prefix and, in the truncated case, the
|
||||
/// <c>DiagnosticMessage</c> — a full reserve of margin under it. Budgeting against the pipe
|
||||
@@ -1246,8 +1246,8 @@ public sealed class WorkerPipeSession
|
||||
_activeCommandTasks.Add(commandTask);
|
||||
}
|
||||
|
||||
// WRK-33: every other discarded task in this file is wrapped with ObserveFaultWhenever
|
||||
// (NEXT-04 discipline) so a fault reaching the finalizer unobserved is impossible. This
|
||||
// Every other discarded task in this file is wrapped with ObserveFaultWhenever
|
||||
// (the fault-observation discipline) so a fault reaching the finalizer unobserved is impossible. This
|
||||
// wrapper task was the one exception — ObserveCommandTaskAsync only swallows
|
||||
// OperationCanceledException, so any other exception escaping ProcessCommandAsync's own
|
||||
// catch path (e.g. a future edit, or a failure past TryWriteFaultAsync's narrow filter)
|
||||
@@ -1380,7 +1380,7 @@ public sealed class WorkerPipeSession
|
||||
if ((!string.IsNullOrEmpty(snapshot.CurrentCommandCorrelationId) || snapshot.StaCallInProgress)
|
||||
&& staleFor <= _sessionOptions.HeartbeatStuckCeiling)
|
||||
{
|
||||
// A command is in flight, or an STA call outside the dispatcher (the alarm poll, WRK-27) is
|
||||
// A command is in flight, or an STA call outside the dispatcher (the alarm poll) is
|
||||
// executing, and we are still within the defensive suppression ceiling — the STA is busy
|
||||
// doing that work, not hung. The next MarkActivity() in StaRuntime.ProcessQueuedCommands
|
||||
// will refresh LastActivityUtc once the work returns, at which point this branch stops
|
||||
@@ -1402,7 +1402,7 @@ public sealed class WorkerPipeSession
|
||||
// the StaHung fault just sent. Without this the heartbeat loop keeps
|
||||
// advertising a non-faulted state that contradicts the fault.
|
||||
_state = WorkerState.Faulted;
|
||||
// WRK-34: CommandMethod is a method-name field, not a correlation-id field, so a
|
||||
// CommandMethod is a method-name field, not a correlation-id field, so a
|
||||
// consumer reading it (dashboard, gateway logs) shouldn't see a bare correlation id
|
||||
// there. Use a stable label and carry the stuck command's correlation id (if any) in
|
||||
// the diagnostic message instead.
|
||||
|
||||
@@ -290,12 +290,6 @@ public sealed class FailoverAlarmConsumer : IMxAccessAlarmConsumer
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Both values come from ONE call to the active child, so the snapshot
|
||||
/// and its verdict cannot end up describing different children across a
|
||||
/// failover. A failover to the subtag standby therefore reports
|
||||
/// not-truncated — correctly, since that child performs no capped fetch.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms(out bool truncated)
|
||||
{
|
||||
if (disposed) throw new ObjectDisposedException(nameof(FailoverAlarmConsumer));
|
||||
|
||||
@@ -108,12 +108,9 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource
|
||||
public event EventHandler<SubtagValueChange>? ValueChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Idempotent per address: an address already advised is skipped
|
||||
/// rather than re-registered.
|
||||
/// </remarks>
|
||||
public void Advise(IReadOnlyCollection<string> itemAddresses)
|
||||
{
|
||||
// Idempotent per address: an address already advised is skipped rather than re-registered.
|
||||
if (itemAddresses is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemAddresses));
|
||||
@@ -144,12 +141,9 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// Writes with MXAccess user id 0 — always an unsecured Write, never
|
||||
/// WriteSecured semantics.
|
||||
/// </remarks>
|
||||
public void Write(string itemAddress, object? value)
|
||||
{
|
||||
// Writes with MXAccess user id 0 — always an unsecured Write, never WriteSecured semantics.
|
||||
if (itemAddress is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemAddress));
|
||||
|
||||
@@ -85,12 +85,7 @@ public sealed class MxAccessBaseEventSink : IMxAccessEventSink, IWriteCompletion
|
||||
/// </summary>
|
||||
public MxAccessValueCache ValueCache => valueCache;
|
||||
|
||||
/// <summary>
|
||||
/// The OnWriteComplete completion cache populated by this sink. Exposed
|
||||
/// via <see cref="IWriteCompletionCacheProvider"/> so the
|
||||
/// MxAccessSession can share the same instance with the write command
|
||||
/// executor's bounded completion wait.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public MxAccessWriteCompletionCache WriteCompletionCache => writeCompletionCache;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1025,7 +1025,7 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
|
||||
/// <summary>
|
||||
/// Captures the write-completion baseline for <paramref name="serverHandle"/> /
|
||||
/// <paramref name="itemHandle"/> immediately before a Write/Write2/WriteSecured/
|
||||
/// WriteSecured2 COM call. Pumps once first (WRK-32) so an <c>OnWriteComplete</c>
|
||||
/// WriteSecured2 COM call. Pumps once first so an <c>OnWriteComplete</c>
|
||||
/// already queued for this handle — most commonly the still-undelivered callback of an
|
||||
/// earlier write to the same tag that this worker gave up waiting on — folds into the
|
||||
/// baseline instead of being available to ride this write's reply.
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
||||
/// 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
|
||||
/// cloning. 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
|
||||
@@ -231,7 +231,7 @@ public sealed class MxAccessEventQueue
|
||||
// lock, so a large gateway-pulled drain kept the STA's next Enqueue blocked for the whole
|
||||
// sizing walk — exactly when a burst of COM callbacks needs the lock. Measuring on the
|
||||
// enqueuing thread is safe on both counts: that thread owns this event exclusively at this
|
||||
// point, and WRK-11 (see the class remarks) makes the hand-off final — nothing mutates an
|
||||
// point, and the ownership invariant (see the class remarks) makes the hand-off final — nothing mutates an
|
||||
// event after Enqueue, so a memoized size can never go stale.
|
||||
//
|
||||
// Enqueue itself stamps the worker sequence and worker timestamp, and those must stay under
|
||||
@@ -263,7 +263,7 @@ public sealed class MxAccessEventQueue
|
||||
throw new MxAccessEventQueueOverflowException(capacity);
|
||||
}
|
||||
|
||||
// WRK-11: stamp the sequence/timestamp on the caller's own event and
|
||||
// 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
|
||||
@@ -374,7 +374,7 @@ public sealed class MxAccessEventQueue
|
||||
/// <remarks>
|
||||
/// The size decision happens inside the queue lock, so an event is dequeued only once it is
|
||||
/// known to fit: an event that does not fit stays at the head for the next call and is never
|
||||
/// lost (WRK-21). Per-event cost is the serialized <c>WorkerEvent</c> size measured once at
|
||||
/// lost. Per-event cost is the serialized <c>WorkerEvent</c> size measured once at
|
||||
/// enqueue time — which already includes the event's own tag and length prefix, the same
|
||||
/// shape the reply's <c>events</c> repeated field packs it into — plus
|
||||
/// <see cref="RepeatedFieldOverheadBytes"/> of pure slack, so the running total stays
|
||||
@@ -528,17 +528,22 @@ public sealed class MxAccessEventQueue
|
||||
// exactly as before.
|
||||
private readonly struct QueuedEvent
|
||||
{
|
||||
/// <summary>Initializes a queued entry from an event and its serialized size.</summary>
|
||||
/// <param name="workerEvent">The queued event.</param>
|
||||
/// <param name="size">The serialized size of the event in bytes.</param>
|
||||
public QueuedEvent(WorkerEvent workerEvent, int size)
|
||||
{
|
||||
Event = workerEvent;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
// The queued event, returned unchanged by TryDequeue and both Drain overloads.
|
||||
/// <summary>Gets the queued event, returned unchanged by TryDequeue and both Drain overloads.</summary>
|
||||
public WorkerEvent Event { get; }
|
||||
|
||||
// Serialized size of Event, valid for the queue's whole lifetime because an enqueued event
|
||||
// is never mutated again (WRK-11).
|
||||
/// <summary>
|
||||
/// Gets the serialized size of <see cref="Event"/>, valid for the queue's whole lifetime because an enqueued event
|
||||
/// is never mutated again.
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
// the StaCommandDispatcher (so it does not inflate PendingCommandCount or perturb command dispatch
|
||||
// ordering), which means CaptureHeartbeat would otherwise see no in-flight activity during a long
|
||||
// poll and the watchdog would fault the session at the 15 s grace instead of the 75 s ceiling
|
||||
// granted to dispatched commands. Surfacing the poll on the heartbeat closes that asymmetry
|
||||
// (WRK-27). Volatile: written on the STA thread, read on the heartbeat thread.
|
||||
// granted to dispatched commands. Surfacing the poll on the heartbeat closes that asymmetry.
|
||||
// Volatile: written on the STA thread, read on the heartbeat thread.
|
||||
private volatile bool staAlarmPollInProgress;
|
||||
private bool disposed;
|
||||
|
||||
@@ -204,6 +204,7 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
/// environment variable; a missing or invalid value falls back to
|
||||
/// <see cref="MxAccessCommandExecutor.DefaultWriteCompletionTimeout"/>.
|
||||
/// </summary>
|
||||
/// <returns>The resolved write-completion timeout.</returns>
|
||||
internal static TimeSpan ResolveWriteCompletionTimeout()
|
||||
{
|
||||
string? value = Environment.GetEnvironmentVariable(WorkerLaunchEnvironment.WriteCompletionWaitMilliseconds);
|
||||
@@ -344,8 +345,8 @@ public sealed class MxAccessStaSession : IWorkerRuntimeSession
|
||||
await staRuntime.InvokeAsync(
|
||||
() =>
|
||||
{
|
||||
// Advertise the poll to the watchdog for exactly the span of the COM call
|
||||
// (WRK-27): set on the STA thread immediately before the affinity check and
|
||||
// Advertise the poll to the watchdog for exactly the span of the COM call:
|
||||
// set on the STA thread immediately before the affinity check and
|
||||
// PollOnce, clear in the finally so a heartbeat captured mid-poll reports
|
||||
// StaCallInProgress and one captured after does not.
|
||||
staAlarmPollInProgress = true;
|
||||
|
||||
@@ -203,6 +203,9 @@ public sealed class MxAccessWriteCompletionCache
|
||||
/// </remarks>
|
||||
private readonly struct CompletionEntry
|
||||
{
|
||||
/// <summary>Initializes a completion entry.</summary>
|
||||
/// <param name="version">The monotonic completion version.</param>
|
||||
/// <param name="statuses">The statuses reported for the completion.</param>
|
||||
public CompletionEntry(
|
||||
ulong version,
|
||||
RepeatedField<MxStatusProxy> statuses)
|
||||
@@ -211,8 +214,10 @@ public sealed class MxAccessWriteCompletionCache
|
||||
Statuses = statuses;
|
||||
}
|
||||
|
||||
/// <summary>Gets the monotonic completion version.</summary>
|
||||
public ulong Version { get; }
|
||||
|
||||
/// <summary>Gets the statuses reported for the completion.</summary>
|
||||
public RepeatedField<MxStatusProxy> Statuses { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,13 +152,6 @@ public sealed class SubtagAlarmConsumer : IMxAccessAlarmConsumer
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <paramref name="truncated"/> is always <see langword="false"/>: subtag
|
||||
/// mode is advise-driven over a fixed watch list, so there is no bulk
|
||||
/// fetch and no per-fetch cap to hit. Subtag snapshots are lower-fidelity
|
||||
/// in other ways, which <c>MxAlarmSnapshotRecord.Degraded</c> already
|
||||
/// reports; truncation is not one of them.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms(out bool truncated)
|
||||
{
|
||||
truncated = false;
|
||||
|
||||
@@ -381,18 +381,6 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// This is the set the gateway's reconcile pass diffs its cache
|
||||
/// against, so an alarm missing here is what the gateway reads as a
|
||||
/// Clear. After a truncated fetch the snapshot deliberately still
|
||||
/// carries alarms the capped reply could not mention — see
|
||||
/// <see cref="PollOnce"/>. Erring toward "still active" keeps the feed
|
||||
/// at-least-once: a stale entry is repaired by the next sub-cap poll,
|
||||
/// whereas a dropped one is broadcast as a Clear that never happened.
|
||||
/// The snapshot and <paramref name="truncated"/> are produced under one
|
||||
/// <c>syncRoot</c> acquisition, the same one <see cref="FoldFetch"/>
|
||||
/// writes them both under, so no poll can interleave between them.
|
||||
/// </remarks>
|
||||
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms(out bool truncated)
|
||||
{
|
||||
if (disposed) throw new ObjectDisposedException(nameof(WnWrapAlarmConsumer));
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
||||
/// maximum: byte-heavy events (large string or array <c>MxValue</c>s) overshoot the frame max
|
||||
/// long before the count ceiling is reached, and the writer's per-frame rejection then
|
||||
/// destroys events that were already removed from the queue. The byte-budgeted drain sizes
|
||||
/// the reply while draining, so an event that does not fit is never dequeued (WRK-21), and
|
||||
/// the reply while draining, so an event that does not fit is never dequeued, and
|
||||
/// this result carries the truncation facts the reply's <c>DiagnosticMessage</c> reports —
|
||||
/// no contract change is needed to express them.
|
||||
/// Plain constructor and get-only properties: the worker targets .NET Framework 4.8, which
|
||||
|
||||
@@ -11,8 +11,7 @@ public sealed class WorkerRuntimeHeartbeatSnapshot
|
||||
/// <param name="lastEventSequence">Sequence number of the most recent event.</param>
|
||||
/// <param name="currentCommandCorrelationId">Correlation ID of the in-flight command.</param>
|
||||
/// <param name="staCallInProgress">
|
||||
/// True while an STA call outside the command dispatcher is executing (currently the alarm poll,
|
||||
/// WRK-27). The watchdog treats this like an in-flight command: it suppresses the stale-STA fault
|
||||
/// True while an STA call outside the command dispatcher is executing (currently the alarm poll). The watchdog treats this like an in-flight command: it suppresses the stale-STA fault
|
||||
/// up to the stuck ceiling instead of the shorter grace, so a healthy-but-slow poll does not fault
|
||||
/// a healthy session. Named generically so any future non-dispatcher STA work reuses it.
|
||||
/// </param>
|
||||
@@ -50,7 +49,7 @@ public sealed class WorkerRuntimeHeartbeatSnapshot
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether an STA call outside the command dispatcher (the alarm poll) is
|
||||
/// executing. When true the watchdog grants the poll the same grace-to-ceiling suppression as a
|
||||
/// dispatched command (WRK-27).
|
||||
/// dispatched command.
|
||||
/// </summary>
|
||||
public bool StaCallInProgress { get; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user