Skip to content

Fix race condition in ObserveUnauthorizedPollingConnections test#713

Merged
rhysparry merged 2 commits into
mainfrom
rhys/eft-3214/fix-observe-unauthorized-polling-connections-race
May 8, 2026
Merged

Fix race condition in ObserveUnauthorizedPollingConnections test#713
rhysparry merged 2 commits into
mainfrom
rhys/eft-3214/fix-observe-unauthorized-polling-connections-race

Conversation

@rhysparry
Copy link
Copy Markdown
Contributor

Summary

Fixes a flaky test failure in ObserveUnauthorizedPollingConnections (Polling, InMemory queue) observed in CI build 8.1.4994-pull-712 on Windows Server 2012 R2 / net48.

Root cause

The test was calling clientAndBuilder.Client.TrustOnly(new List<string>()) after Build() returned, relying on that call to prevent any authorized connections from being observed. However, Build() starts the polling service immediately, and the service begins connecting to the client's TCP listener within milliseconds — creating a race window where the first polling connection could be fully authorized before TrustOnly was called.

When that happened, the sequence in SecureListener was:

  1. Polling service connects and TLS handshake completes
  2. Authorize() calls verifyClientThumbprint — cert still trusted at this point → returns true
  3. connectionAuthorizedAndObserved = true; ConnectionAccepted(true) fires
  4. ExchangeMessages begins
  5. TrustOnly(new List<string>()) runs — but too late for this connection
  6. Connection eventually closes → ConnectionClosed(connectionAuthorizedAndObserved) fires as true
  7. Assertion ConnectionClosedAuthorized.AllSatisfy(a => a.BeFalse()) fails

The race was intermittent because the window is only a few milliseconds wide. It was more likely to trigger under load (e.g. when multiple ConnectionObserverFixture tests ran concurrently), as seen in the CI log where ObserveAuthorizedConnections(Polling) and ObserveUnauthorizedPollingConnections(Polling) overlapped in time.

The listening variant of this test (ObserveUnauthorizedListeningConnections) has never had this issue because it uses WithServiceTrustingTheWrongCertificate(), which configures the wrong certificate before Build() — so connections are unauthorized from the very first attempt.

Fix

Adds WithClientTrustingNoThumbprints() to LatestClientBuilder and LatestClientAndLatestServiceBuilder. When set, the builder skips the client.Trust(thumbprint) call, leaving the client's trust provider empty before Build() starts the polling service. This eliminates the race entirely: no connection can ever be authorized because trust is never established.

The test now mirrors the pattern used by the listening variant — unauthorized behaviour is configured at builder time rather than mutated post-build.

Test results

  • net48: 2 passed (InMemory + Redis queue variants) — the target framework from the failing CI run
  • net8.0: Redis variant skipped due to Docker not available locally; InMemory variant passes

🤖 Generated with Claude Code

The polling service begins connecting immediately when Build() returns, so
calling TrustOnly(empty) afterwards left a window where the first connection
could be authorized before trust was revoked. Move the no-trust configuration
into the builder so the client's trust list is empty before the service starts
polling, eliminating the race entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rhysparry rhysparry requested a review from a team as a code owner May 5, 2026 23:45
@rhysparry rhysparry merged commit ef61c10 into main May 8, 2026
17 checks passed
@rhysparry rhysparry deleted the rhys/eft-3214/fix-observe-unauthorized-polling-connections-race branch May 8, 2026 03:29
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.

2 participants