docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

Adds <summary>/<param>/<returns>/<inheritdoc> where missing and removes
project bookkeeping IDs (task/tracking refs) from shipped code comments,
so the docs read cleanly and CommentChecker is quiet except for known
false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc).
Doc/comment-only; no logic changed; solution builds clean.
This commit is contained in:
Joseph Doherty
2026-07-07 12:38:39 -04:00
parent 384dbd7d36
commit 9cad9ed0fc
375 changed files with 1899 additions and 2493 deletions
@@ -100,8 +100,8 @@ internal sealed class EventPump : IAsyncDisposable
// false immediately, which we account for via the EventsDropped counter.
// We deliberately do NOT use BoundedChannelFullMode.DropWrite — that
// would silently discard the new event inside Channel<T> without
// surfacing the drop on a counter (Driver.Galaxy-005: keep the comment
// and the FullMode value consistent).
// surfacing the drop on a counter (keep this comment and the FullMode
// value consistent).
FullMode = BoundedChannelFullMode.Wait,
SingleReader = true,
SingleWriter = true,
@@ -68,6 +68,7 @@ public sealed class GalaxyMxSession : IAsyncDisposable
/// </summary>
/// <param name="clientOptions">The MX gateway client options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task ConnectAsync(MxGatewayClientOptions clientOptions, CancellationToken cancellationToken)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -107,6 +108,7 @@ public sealed class GalaxyMxSession : IAsyncDisposable
/// </summary>
/// <param name="clientOptions">The MX gateway client options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task RecreateAsync(MxGatewayClientOptions clientOptions, CancellationToken cancellationToken)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -136,6 +138,7 @@ public sealed class GalaxyMxSession : IAsyncDisposable
public MxGatewaySession? Session => _session;
/// <summary>Disposes the session and underlying gateway client resources.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async ValueTask DisposeAsync()
{
if (_disposed) return;
@@ -8,6 +8,6 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
/// </summary>
internal sealed record GalaxySubscriptionHandle(long SubscriptionId) : ISubscriptionHandle
{
/// <summary>Gets the diagnostic identifier for the subscription.</summary>
/// <inheritdoc />
public string DiagnosticId => $"galaxy-sub-{SubscriptionId}";
}
@@ -32,11 +32,7 @@ internal sealed class GatewayGalaxyAlarmAcknowledger : IGalaxyAlarmAcknowledger
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>Acknowledges an alarm via the gateway.</summary>
/// <param name="alarmFullReference">The full reference path of the alarm to acknowledge.</param>
/// <param name="comment">An operator-supplied comment attached to the acknowledgement.</param>
/// <param name="operatorUser">The name of the operator performing the acknowledgement.</param>
/// <param name="cancellationToken">A token to cancel the asynchronous operation.</param>
/// <inheritdoc />
public async Task AcknowledgeAsync(
string alarmFullReference,
string comment,
@@ -39,6 +39,7 @@ internal sealed class GatewayGalaxyAlarmFeed : IGalaxyAlarmFeed
/// </summary>
/// <param name="request">The stream request parameters.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>The async stream of decoded alarm-feed messages.</returns>
internal delegate IAsyncEnumerable<AlarmFeedMessage> AlarmStreamFactory(
StreamAlarmsRequest request, CancellationToken cancellationToken);
@@ -90,7 +91,7 @@ internal sealed class GatewayGalaxyAlarmFeed : IGalaxyAlarmFeed
_clientTag = new KeyValuePair<string, object?>("galaxy.client", clientName ?? "<unknown>");
}
/// <summary>Starts the alarm feed by opening the stream and processing messages in a background task.</summary>
/// <inheritdoc />
public void Start()
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -261,6 +262,7 @@ internal sealed class GatewayGalaxyAlarmFeed : IGalaxyAlarmFeed
};
/// <summary>Releases the alarm feed resources and stops the background stream task.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async ValueTask DisposeAsync()
{
if (_disposed) return;
@@ -115,11 +115,7 @@ public sealed class GatewayGalaxyDataWriter : IGalaxyDataWriter
return null;
}
/// <summary>Writes values to Galaxy tags through the gateway.</summary>
/// <param name="writes">The write requests.</param>
/// <param name="securityResolver">Function to resolve security classification per tag.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The write results per request.</returns>
/// <inheritdoc />
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
IReadOnlyList<WriteRequest> writes,
Func<string, SecurityClassification> securityResolver,
@@ -37,11 +37,7 @@ public sealed class GatewayGalaxySubscriber : IGalaxySubscriber
_logger = logger ?? NullLogger.Instance;
}
/// <summary>Subscribes to a bulk list of Galaxy references with optional buffered update interval.</summary>
/// <param name="fullReferences">The full Galaxy tag references to subscribe to.</param>
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that returns a list of subscribe results.</returns>
/// <inheritdoc />
public async Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
{
@@ -142,10 +138,7 @@ public sealed class GatewayGalaxySubscriber : IGalaxySubscriber
/// <returns><c>true</c> if the interval should be recorded as applied; otherwise <c>false</c>.</returns>
internal static bool ClassifyIntervalReply(ProtocolStatusCode? code) => code == ProtocolStatusCode.Ok;
/// <summary>Unsubscribes from a bulk list of item handles.</summary>
/// <param name="itemHandles">The item handles to unsubscribe from.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the unsubscribe operation.</returns>
/// <inheritdoc />
public async Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
{
if (itemHandles.Count == 0) return;
@@ -159,9 +152,7 @@ public sealed class GatewayGalaxySubscriber : IGalaxySubscriber
.ConfigureAwait(false);
}
/// <summary>Streams Galaxy MX events asynchronously.</summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An async enumerable of MX events.</returns>
/// <inheritdoc />
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
{
var session = _session.Session
@@ -24,6 +24,7 @@ internal interface IGalaxyAlarmAcknowledger
/// OPC UA session by the server-side ACL layer before reaching the driver.
/// </param>
/// <param name="cancellationToken">Cancels the gateway RPC.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task AcknowledgeAsync(
string alarmFullReference,
string comment,
@@ -26,6 +26,7 @@ public interface IGalaxyDataWriter
/// (the safest default — non-secured Write).
/// </param>
/// <param name="cancellationToken">Aborts the in-flight batch.</param>
/// <returns>One <see cref="WriteResult"/> per request entry, in input order.</returns>
Task<IReadOnlyList<WriteResult>> WriteAsync(
IReadOnlyList<WriteRequest> writes,
Func<string, SecurityClassification> securityResolver,
@@ -20,12 +20,14 @@ public interface IGalaxySubscriber
/// <param name="fullReferences">The list of tag references to subscribe to.</param>
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>One <see cref="SubscribeResult"/> per requested reference, in input order.</returns>
Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken);
/// <summary>Unsubscribe a batch of item handles obtained from <see cref="SubscribeBulkAsync"/>.</summary>
/// <param name="itemHandles">The item handles to unsubscribe.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken);
/// <summary>
@@ -34,5 +36,6 @@ public interface IGalaxySubscriber
/// its <see cref="SubscriptionRegistry"/>.
/// </summary>
/// <param name="cancellationToken">Cancellation token for the stream.</param>
/// <returns>An asynchronous stream of <see cref="MxEvent"/>s as they arrive from the gateway.</returns>
IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken);
}
@@ -37,6 +37,7 @@ internal static class MxAccessSeverityMapper
/// <see cref="AlarmSeverity"/> + OPC UA Part 9 numeric severity tuple.
/// </summary>
/// <param name="rawMxAccessSeverity">The raw MXAccess severity value (0-999 range, clamped if out of range).</param>
/// <returns>The mapped four-bucket <see cref="AlarmSeverity"/> and its OPC UA Part 9 numeric severity.</returns>
public static (AlarmSeverity Bucket, int OpcUaSeverity) Map(int rawMxAccessSeverity)
{
if (rawMxAccessSeverity < 250)
@@ -15,6 +15,7 @@ internal static class MxValueDecoder
{
/// <summary>Decodes a gateway MxValue into a boxed CLR object.</summary>
/// <param name="value">The MxValue to decode, or null.</param>
/// <returns>The decoded boxed CLR value, or null when <paramref name="value"/> is null or carries a null MxValue.</returns>
public static object? Decode(MxValue? value)
{
if (value is null) return null;
@@ -25,7 +25,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
/// <para>
/// <b>Replay</b>: caller-supplied callback that re-establishes every active
/// subscription. Production wraps gw's <c>ReplaySubscriptionsCommand</c>
/// (mxaccessgw issue #0.3); when that's not available, the callback falls
/// (tracked in the mxaccessgw backlog); when that's not available, the callback falls
/// back to walking the SubscriptionRegistry and re-issuing SubscribeBulk for
/// every tracked tag.
/// </para>
@@ -147,6 +147,7 @@ public sealed class ReconnectSupervisor : IDisposable
/// and for orchestration that wants to gate calls on recovery completing.
/// </summary>
/// <param name="cancellationToken">Token to cancel the wait operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task WaitForHealthyAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested && IsDegraded)
@@ -19,10 +19,6 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
internal sealed class SubscriptionRegistry
{
private readonly ConcurrentDictionary<long, SubscriptionEntry> _bySubscriptionId = new();
// Driver.Galaxy-012: use ImmutableHashSet<long> for the reverse map so removal is
// O(log n) instead of "rebuild the entire ConcurrentBag from a LINQ filter on every
// unsubscribe"; reads are lock-free because the immutable snapshot is published
// atomically via ConcurrentDictionary AddOrUpdate.
private readonly ConcurrentDictionary<int, ImmutableHashSet<long>> _subscribersByItemHandle = new();
// Forward index for the Galaxy writer: fullRef (case-insensitive) → live item handle.
// Maintained in lock-step with _subscribersByItemHandle; entries are cleaned up when
@@ -38,6 +34,7 @@ internal sealed class SubscriptionRegistry
public int TrackedItemHandleCount => _subscribersByItemHandle.Count;
/// <summary>Allocate a fresh subscription id. Monotonic; unique per registry lifetime.</summary>
/// <returns>The newly allocated subscription id.</returns>
public long NextSubscriptionId() => Interlocked.Increment(ref _nextSubscriptionId);
/// <summary>
@@ -75,8 +72,6 @@ internal sealed class SubscriptionRegistry
foreach (var binding in entry.Bindings)
{
if (binding.ItemHandle <= 0) continue;
// Driver.Galaxy-012: ImmutableHashSet.Remove is O(log n) and the result is
// published atomically — no need to rebuild from a LINQ filter.
if (!_subscribersByItemHandle.TryGetValue(binding.ItemHandle, out var set)) continue;
var remaining = set.Remove(subscriptionId);
if (remaining.IsEmpty)
@@ -97,12 +92,6 @@ internal sealed class SubscriptionRegistry
/// Look up the (subscription id, full reference) pairs that should receive an
/// OnDataChange for the given gw item handle. Returns empty when nobody subscribes.
/// </summary>
/// <remarks>
/// Driver.Galaxy-012: O(1) per subscriber via the per-entry
/// <c>FullRefByItemHandle</c> index, rather than a <c>FirstOrDefault</c> linear
/// scan of the binding list. At 50k tags / 1Hz this turns each dispatch from a
/// 50k-element scan into a single dictionary lookup.
/// </remarks>
/// <param name="itemHandle">The gateway item handle.</param>
/// <returns>A list of subscription and reference pairs for the item handle.</returns>
public IReadOnlyList<(long SubscriptionId, string FullReference)> ResolveSubscribers(int itemHandle)
@@ -153,6 +142,7 @@ internal sealed class SubscriptionRegistry
}
/// <summary>Snapshot every active binding for diagnostic output.</summary>
/// <returns>A flattened list of every tag binding across all tracked subscriptions.</returns>
public IReadOnlyList<TagBinding> SnapshotAllBindings() =>
[.. _bySubscriptionId.Values.SelectMany(entry => entry.Bindings)];
@@ -161,6 +151,7 @@ internal sealed class SubscriptionRegistry
/// Used by the reconnect replay path so it can re-issue SubscribeBulk per subscription
/// and then <see cref="Rebind"/> each one with the post-reconnect item handles.
/// </summary>
/// <returns>Every tracked subscription id paired with its current tag bindings.</returns>
public IReadOnlyList<(long SubscriptionId, IReadOnlyList<TagBinding> Bindings)> SnapshotEntries() =>
[.. _bySubscriptionId.Values.Select(entry => (entry.SubscriptionId, entry.Bindings))];
@@ -178,7 +169,6 @@ internal sealed class SubscriptionRegistry
// Drop this subscription from every reverse-map set it currently appears in. The
// pre-reconnect item handles are stale once the gw re-issues fresh ones.
// Driver.Galaxy-012: ImmutableHashSet.Remove is O(log n) — no LINQ rebuild.
foreach (var binding in oldEntry.Bindings)
{
if (binding.ItemHandle <= 0) continue;
@@ -210,8 +200,8 @@ internal sealed class SubscriptionRegistry
/// <summary>
/// Per-subscription bookkeeping. <see cref="FullRefByItemHandle"/> is an index
/// over <see cref="Bindings"/> keyed by item handle so <c>ResolveSubscribers</c>
/// is O(1) per subscriber instead of a linear scan of every binding
/// (Driver.Galaxy-012). Failed bindings (item handle ≤ 0) are excluded from the
/// is O(1) per subscriber instead of a linear scan of every binding.
/// Failed bindings (item handle ≤ 0) are excluded from the
/// index because the EventPump only dispatches for positive handles.
/// </summary>
private sealed class SubscriptionEntry
@@ -11,13 +11,9 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
internal sealed class TracedGalaxyDataWriter(IGalaxyDataWriter inner, string clientName) : IGalaxyDataWriter
{
/// <inheritdoc />
/// <remarks>No span — this is a local cache-clear operation, not a gateway round-trip.</remarks>
public void InvalidateHandleCaches() => inner.InvalidateHandleCaches();
/// <summary>Writes data to Galaxy while recording telemetry span.</summary>
/// <param name="writes">The list of write requests to process.</param>
/// <param name="securityResolver">Function to resolve security classification for tag references.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <inheritdoc />
public async Task<IReadOnlyList<WriteResult>> WriteAsync(
IReadOnlyList<WriteRequest> writes,
Func<string, SecurityClassification> securityResolver,
@@ -10,10 +10,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
/// </summary>
internal sealed class TracedGalaxySubscriber(IGalaxySubscriber inner, string clientName) : IGalaxySubscriber
{
/// <summary>Subscribes to multiple Galaxy tags in bulk with tracing.</summary>
/// <param name="fullReferences">The full tag references to subscribe to.</param>
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <inheritdoc />
public async Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
{
@@ -35,9 +32,7 @@ internal sealed class TracedGalaxySubscriber(IGalaxySubscriber inner, string cli
}
}
/// <summary>Unsubscribes from multiple Galaxy tags in bulk with tracing.</summary>
/// <param name="itemHandles">The item handles to unsubscribe from.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <inheritdoc />
public async Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
{
using var activity = GalaxyTelemetry.ActivitySource.StartActivity("galaxy.unsubscribe_bulk");
@@ -54,12 +49,10 @@ internal sealed class TracedGalaxySubscriber(IGalaxySubscriber inner, string cli
}
}
/// <summary>
/// Streaming RPC — one parent span covers the entire stream lifetime. Per-event
/// spans would dominate the trace volume at 50k tags / 1Hz; ops gets per-event
/// visibility through <see cref="EventPump"/>'s metrics in PR 6.2 instead.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
// Streaming RPC — one parent span covers the entire stream lifetime. Per-event
// spans would dominate the trace volume at 50k tags / 1Hz; ops gets per-event
// visibility through EventPump's metrics instead.
/// <inheritdoc />
public async IAsyncEnumerable<MxEvent> StreamEventsAsync(
[EnumeratorCancellation] CancellationToken cancellationToken)
{