docs(src): add missing XML docs and strip tracking-ID comments
Sweep of 203 source files resolving CommentChecker findings: add <summary>/<param>/<returns>/<inheritdoc> where missing, and remove resolved task/issue tracking markers (Tests-NNN, Worker-NNN, Server-NNN, Task N) from code comments. Comment/doc-only — no logic changes. Server+Tests build clean under TreatWarningsAsErrors.
This commit is contained in:
@@ -13,21 +13,21 @@ using ZB.MOM.WW.MxGateway.Tests.TestSupport;
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Sessions;
|
||||
|
||||
/// <summary>
|
||||
/// Concurrency and disposal regression tests for <see cref="GatewaySession"/>.
|
||||
/// Server-015 and Server-016 audited the split lock discipline between
|
||||
/// <c>_syncRoot</c> (state transitions) and <c>_closeLock</c> (close serialization)
|
||||
/// and the un-gated <c>DisposeAsync</c>; these tests pin the post-fix behavior.
|
||||
/// Concurrency and disposal regression tests for <see cref="GatewaySession"/>,
|
||||
/// covering the split lock discipline between <c>_syncRoot</c> (state transitions)
|
||||
/// and <c>_closeLock</c> (close serialization) and the gated <c>DisposeAsync</c>.
|
||||
/// </summary>
|
||||
public sealed class GatewaySessionTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-015 regression. A <c>TransitionTo(Ready)</c> issued after
|
||||
/// A <c>TransitionTo(Ready)</c> issued after
|
||||
/// <see cref="GatewaySession.CloseAsync"/> has set <see cref="SessionState.Closing"/>
|
||||
/// must not flip the session back to <see cref="SessionState.Ready"/>. The
|
||||
/// blocking worker shutdown keeps <c>CloseAsync</c> parked between the
|
||||
/// <c>Closing</c> write and the <c>Closed</c> write, which is precisely the
|
||||
/// window the audit identified.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TransitionTo_AfterCloseStarted_DoesNotOverwriteClosing()
|
||||
{
|
||||
@@ -54,12 +54,13 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-015 regression. Once <see cref="GatewaySession.CloseAsync"/> finishes,
|
||||
/// Once <see cref="GatewaySession.CloseAsync"/> finishes,
|
||||
/// <see cref="GatewaySession.MarkFaulted"/> must not be able to move the
|
||||
/// session out of <see cref="SessionState.Closed"/> either — the close path's
|
||||
/// terminal write goes through the same <c>_syncRoot</c> the rest of the state
|
||||
/// machine uses, so the existing "Closed is terminal" invariant holds.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MarkFaulted_AfterCloseCompletes_DoesNotResurrectSession()
|
||||
{
|
||||
@@ -76,7 +77,7 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-028 regression. A <see cref="GatewaySession.MarkFaulted"/> issued
|
||||
/// A <see cref="GatewaySession.MarkFaulted"/> issued
|
||||
/// while <see cref="GatewaySession.CloseAsync"/> is parked between its
|
||||
/// <c>Closing</c> and <c>Closed</c> writes must not break the close path's
|
||||
/// terminal contract: the in-flight close runs to <c>Closed</c>, the fault
|
||||
@@ -86,6 +87,7 @@ public sealed class GatewaySessionTests
|
||||
/// Faulted" — this test pins the resolved end state so a future tightening
|
||||
/// of <c>MarkFaulted</c> cannot silently regress it.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task MarkFaulted_DuringInFlightClose_PreservesFaultButYieldsToClose()
|
||||
{
|
||||
@@ -113,11 +115,12 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-016 regression. <see cref="GatewaySession.DisposeAsync"/> must wait
|
||||
/// <see cref="GatewaySession.DisposeAsync"/> must wait
|
||||
/// for an in-flight <see cref="GatewaySession.CloseAsync"/> before disposing
|
||||
/// its semaphore. Without the fix, the close's <c>_closeLock.Release()</c>
|
||||
/// would race the dispose and raise <see cref="ObjectDisposedException"/>.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisposeAsync_WhileCloseInFlight_WaitsForCloseAndDoesNotThrow()
|
||||
{
|
||||
@@ -149,6 +152,7 @@ public sealed class GatewaySessionTests
|
||||
/// <see cref="ObjectDisposedException"/> from the already-disposed semaphore
|
||||
/// rather than propagating it.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisposeAsync_CalledTwice_DoesNotThrow()
|
||||
{
|
||||
@@ -164,7 +168,7 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issue-1 regression. Concurrent <c>Dispose()</c> calls on the same
|
||||
/// Concurrent <c>Dispose()</c> calls on the same
|
||||
/// <see cref="IEventSubscriberLease"/> — as can happen when a gRPC stream
|
||||
/// completion and a client cancellation both fire at the same time — must
|
||||
/// decrement <c>_activeEventSubscriberCount</c> exactly once, never to −1.
|
||||
@@ -174,6 +178,7 @@ public sealed class GatewaySessionTests
|
||||
/// the count must be exactly 0 and a subsequent single-subscriber attach must
|
||||
/// succeed.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task EventSubscriberLease_ConcurrentDispose_DecrementsCountExactlyOnce()
|
||||
{
|
||||
@@ -222,10 +227,11 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 8 regression. Single-subscriber mode rejects a SECOND concurrent external
|
||||
/// Single-subscriber mode rejects a SECOND concurrent external
|
||||
/// attach with <see cref="SessionManagerErrorCode.EventSubscriberAlreadyActive"/> —
|
||||
/// the legacy guard is preserved unchanged when multi-subscriber is disabled.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AttachEventSubscriber_SingleMode_SecondAttachThrowsAlreadyActive()
|
||||
{
|
||||
@@ -244,10 +250,11 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 8. Multi-subscriber mode allows exactly <c>cap</c> concurrent external
|
||||
/// Multi-subscriber mode allows exactly <c>cap</c> concurrent external
|
||||
/// subscribers; the (cap+1)-th attach throws
|
||||
/// <see cref="SessionManagerErrorCode.EventSubscriberLimitReached"/>.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AttachEventSubscriber_MultiMode_AttachesUpToCapThenThrowsLimitReached()
|
||||
{
|
||||
@@ -280,10 +287,11 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 8. The gateway-owned INTERNAL dashboard subscriber must NOT consume cap
|
||||
/// The gateway-owned INTERNAL dashboard subscriber must NOT consume cap
|
||||
/// budget: with the dashboard mirror running, the full cap of external subscribers is
|
||||
/// still attachable.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AttachEventSubscriber_MultiMode_DashboardMirrorDoesNotConsumeCap()
|
||||
{
|
||||
@@ -322,12 +330,13 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 8 concurrency. Many concurrent attaches in multi-subscriber mode must never
|
||||
/// Many concurrent attaches in multi-subscriber mode must never
|
||||
/// exceed the cap: the count-check-and-increment is atomic under <c>_syncRoot</c>, so
|
||||
/// exactly <c>cap</c> attaches succeed and the rest throw
|
||||
/// <see cref="SessionManagerErrorCode.EventSubscriberLimitReached"/>. The observed
|
||||
/// count never goes above the cap.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AttachEventSubscriber_MultiMode_ConcurrentAttaches_NeverExceedCap()
|
||||
{
|
||||
@@ -396,9 +405,10 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 8. Disposing a subscriber frees a cap slot so a fresh attach succeeds, and a
|
||||
/// Disposing a subscriber frees a cap slot so a fresh attach succeeds, and a
|
||||
/// double-dispose does not double-free the slot (count integrity preserved).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AttachEventSubscriber_MultiMode_DisposeFreesSlotAndDoubleDisposeIsIdempotent()
|
||||
{
|
||||
@@ -434,10 +444,11 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 11. With a positive detach-grace, dropping the last external subscriber must
|
||||
/// With a positive detach-grace, dropping the last external subscriber must
|
||||
/// RETAIN the session (it stays <see cref="SessionState.Ready"/>, not Closed/Faulted)
|
||||
/// and record a detached timestamp so the lease monitor can age it out later.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DetachGrace_LastSubscriberDrops_RetainsSessionAndRecordsDetachedTimestamp()
|
||||
{
|
||||
@@ -461,9 +472,10 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 11. Advancing the clock past the detach-grace window makes the retained,
|
||||
/// Advancing the clock past the detach-grace window makes the retained,
|
||||
/// detached session eligible for close (<see cref="GatewaySession.IsDetachGraceExpired"/>).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DetachGrace_ClockAdvancesPastWindow_SessionBecomesEligibleForClose()
|
||||
{
|
||||
@@ -487,10 +499,11 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 11. Re-attaching a subscriber before the window elapses cancels the grace:
|
||||
/// Re-attaching a subscriber before the window elapses cancels the grace:
|
||||
/// the detached timestamp clears and a subsequent clock advance does NOT make the
|
||||
/// session eligible for close.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DetachGrace_ReattachBeforeExpiry_CancelsGrace()
|
||||
{
|
||||
@@ -520,11 +533,12 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 11. With detach-grace disabled (<c>0</c>), dropping the last subscriber must
|
||||
/// With detach-grace disabled (<c>0</c>), dropping the last subscriber must
|
||||
/// match today's behavior: the session stays <see cref="SessionState.Ready"/> with no
|
||||
/// detached timestamp and is never eligible for a detach-grace close — it lingers only
|
||||
/// until its normal lease expires.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DetachGrace_Disabled_MatchesTodaysBehavior()
|
||||
{
|
||||
@@ -546,13 +560,14 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-055 regression. A FAILED first attach (the distributor never registered a
|
||||
/// A FAILED first attach (the distributor never registered a
|
||||
/// subscriber) must NOT enter the detach-grace window: the catch path's
|
||||
/// <c>DetachEventSubscriber</c> rolls the reserved slot back to 0 but must not stamp
|
||||
/// <c>DetachedAtUtc</c>, because the "last subscriber dropped" semantics only apply once
|
||||
/// a subscriber was successfully registered. A freshly-Ready session whose first attach
|
||||
/// failed must therefore stay out of grace and never become sweep-eligible on that basis.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DetachGrace_FailedFirstAttach_DoesNotEnterGrace()
|
||||
{
|
||||
@@ -583,11 +598,12 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 11. The gateway-owned internal dashboard subscriber must NOT keep a session out
|
||||
/// The gateway-owned internal dashboard subscriber must NOT keep a session out
|
||||
/// of detach-grace: with only the dashboard mirror attached (and no external gRPC
|
||||
/// subscriber), dropping the last external subscriber still enters grace and the
|
||||
/// window still expires.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DetachGrace_DashboardMirrorAlone_DoesNotPreventGraceEntry()
|
||||
{
|
||||
@@ -615,11 +631,12 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task 11. Validates the TOCTOU fix: TryBeginCloseIfExpired atomically re-checks that no
|
||||
/// Validates the TOCTOU fix: TryBeginCloseIfExpired atomically re-checks that no
|
||||
/// subscriber has reattached before flipping to Closing. When the grace window has elapsed but
|
||||
/// a subscriber is attached by the time TryBeginCloseIfExpired runs, it returns false and the
|
||||
/// session remains Ready.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TryBeginCloseIfExpired_ReattachedSubscriberWinsRace_DeclinesClose()
|
||||
{
|
||||
@@ -749,16 +766,16 @@ public sealed class GatewaySessionTests
|
||||
private readonly TaskCompletionSource _shutdownStarted = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly TaskCompletionSource _shutdownReleased = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
/// <summary>Gets the session identifier.</summary>
|
||||
/// <inheritdoc />
|
||||
public string SessionId { get; } = "session-test";
|
||||
|
||||
/// <summary>Gets the worker process identifier.</summary>
|
||||
/// <inheritdoc />
|
||||
public int? ProcessId { get; } = 1234;
|
||||
|
||||
/// <summary>Gets or sets the worker client state.</summary>
|
||||
/// <inheritdoc />
|
||||
public WorkerClientState State { get; private set; } = WorkerClientState.Ready;
|
||||
|
||||
/// <summary>Gets the last recorded heartbeat timestamp.</summary>
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset LastHeartbeatAt { get; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>Gets the count of shutdown invocations.</summary>
|
||||
@@ -768,6 +785,7 @@ public sealed class GatewaySessionTests
|
||||
public int DisposeCount { get; private set; }
|
||||
|
||||
/// <summary>Waits for shutdown to start.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task WaitForShutdownStartAsync()
|
||||
{
|
||||
return _shutdownStarted.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
@@ -811,7 +829,8 @@ public sealed class GatewaySessionTests
|
||||
State = WorkerClientState.Faulted;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Releases the fake worker resources and records the invocation.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
DisposeCount++;
|
||||
@@ -821,16 +840,16 @@ public sealed class GatewaySessionTests
|
||||
|
||||
private sealed class FakeWorkerClient : IWorkerClient
|
||||
{
|
||||
/// <summary>Gets the session identifier.</summary>
|
||||
/// <inheritdoc />
|
||||
public string SessionId { get; } = "session-test";
|
||||
|
||||
/// <summary>Gets the worker process identifier.</summary>
|
||||
/// <inheritdoc />
|
||||
public int? ProcessId { get; } = 1234;
|
||||
|
||||
/// <summary>Gets the worker client state.</summary>
|
||||
/// <inheritdoc />
|
||||
public WorkerClientState State { get; } = WorkerClientState.Ready;
|
||||
|
||||
/// <summary>Gets the last recorded heartbeat timestamp.</summary>
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset LastHeartbeatAt { get; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>Gets the count of dispose invocations.</summary>
|
||||
@@ -861,7 +880,8 @@ public sealed class GatewaySessionTests
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Releases the fake worker resources and records the invocation.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
DisposeCount++;
|
||||
|
||||
Reference in New Issue
Block a user