Skip to content

test: remove TLS handshake-limit counter race from ConnectionAdmissionTests - #234

Merged
SunSi12138 merged 1 commit into
devfrom
feature/issue-229-admission-test-race
Aug 17, 2026
Merged

SunSi12138 merged 1 commit into
devfrom
feature/issue-229-admission-test-race

Conversation

@SunSi12138

Copy link
Copy Markdown
Owner

Summary

Fixes the TlsHandshakeLimitRejectsTheSecondConnectionWhileTheFirstStalls counter race from #229 (first hit on PR #227 CI run 31996872168).

The client observing EOF on the rejected connection is not a sync point for the server accounting: per the #162 / PR #209 design the connection lease is released only after terminal cleanup completes, so ActiveConnections can legitimately still be 2 when the client-side close is observed. The immediate IsEqualTo(1) assertion therefore asserted on a transient state.

Change

test/SharpLink.UnitTests/Server/ConnectionAdmissionTests.cs (test-only, +6/−1):

var secondClosed = await ReadUntilClosedAsync(second);
Ensure(secondClosed, "a connection over the TLS handshake bound must be closed immediately");
// Observing the close from the client side is not a sync point for the server
// accounting: the connection lease is released only after terminal cleanup
// completes, so ActiveConnections may transiently still be 2 here.
await YieldUntilAsync(
    () => server.ConnectionAdmission.ActiveConnections == 1,
    "rejected TLS connection must release its connection slot after terminal cleanup");
await Assert.That(server.ConnectionAdmission.ActiveHandshakes).IsEqualTo(1);
  • Keeps the two verifications the issue asks for separately: (1) the over-bound connection is closed immediately (ReadUntilClosedAsync + Ensure), and (2) terminal cleanup converges the connection accounting to 1.
  • No fixed Task.Delay masking.

Why not the optional test seam

The issue lists the deterministic test seam as optional ("若能加"). The existing ConnectionSlotStaysHeldUntilTerminalCleanupCompletes already proves release-after-terminal-cleanup deterministically via a dispose gate on the scripted transport path; adding a gate to the real TLS socket listener would mean product test hooks for a semantic already covered.

Validation

  • Baseline reproduction (dev @ 25f7cc4, macOS arm64, Release): single-test loop 150 iterations → 114 failed with the exact CI failure (Expected to be 1 but found 2, line 520).
  • After fix, local (macOS): single-test loop 150/150 pass; whole ConnectionAdmissionTests class loop 20/20 pass.
  • After fix, Linux (baremetal, 32 cores, SDK 10.0.110): single-test loop 100/100 pass; full UnitTests suite 1019/1020 pass — the only failure is ProtocolHandshakeTimeoutReleasesBothSlots, a pre-existing, unrelated machine-speed-dependent test that fails identically on baseline dev (10/10 on the same host; passes on the Mac 20/20 and on GitHub's 4-core runners). Root cause: its wait caps at 4000 Task.Yield polls (~190 ms wall time on the 32-core host), which can outrun its own 200 ms handshake-timeout timer. Suggested as a separate follow-up.
  • CI: quick gates this PR as usual.

Closes #229

…nTests

The client observing EOF on the rejected connection is not a sync point
for the server accounting: the connection lease is released only after
terminal cleanup completes (#162 / PR #209 design), so ActiveConnections
may transiently still be 2 when the assertion runs.

Replace the immediate assertion with YieldUntilAsync convergence on
ActiveConnections == 1, keeping the immediate-close verification via
ReadUntilClosedAsync and the stable ActiveHandshakes == 1 check.

Baseline reproduction (dev @ 25f7cc4, macOS arm64): 114/150 iterations
failed with the same failure as PR #227 CI (run 31996872168). With the
fix the local single-test loop passes 150/150.

Closes #229

@SunSi12138 SunSi12138 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against current dev and issue #229. No blocking findings.

The change correctly separates the two assertions: client-side EOF proves the over-handshake-limit connection is rejected promptly, while ActiveConnections == 1 is now awaited as a server-side post-terminal-cleanup invariant. This matches the connection-admission lease lifecycle introduced in #209, where the connection lease is released only after transport terminal cleanup completes. The existing deterministic ConnectionSlotStaysHeldUntilTerminalCleanupCompletes test already covers the release ordering directly, so an additional TLS-specific test seam is not required for this fix.

PR Quick and CodeQL both pass on head 2d31d9ac3b0cbd7d883d309c563b3a7283e6b026.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant