Skip to content

Flaky TestClientTest on Windows CI: random port still collides #1652

Description

@alexander-yevsyukov

Symptom

TestClientTest > post() (:server-testlib:test) intermittently fails the Build on Windows CI job (and, derivatively, JUnit Test Report) with:

java.io.IOException: Failed to bind to address 0.0.0.0/0.0.0.0:<port>
    Caused by: java.net.BindException: Address already in use: bind

Two recent occurrences, both on PR #1650 (whose changes do not touch server-testlib):

Both cleared on re-run — a runner-environment collision, not a product defect.

Root cause

TestClientTest.setUpAll() (TestClientTest.java:66-76) already carries a mitigation:

// Select a port randomly to avoid the intermittent hanging port under Windows.
var port = random(DEFAULT_CLIENT_SERVICE_PORT, DEFAULT_CLIENT_SERVICE_PORT + 1000);
server = Server.atPort(port)...

A random pick from a 1000-port window (50051..51051, base = Client.DEFAULT_CLIENT_SERVICE_PORT) narrows the odds but cannot eliminate them: shared Windows runners host other gRPC-binding tests, and sockets linger in TIME_WAIT between test classes. Since setUpAll is @BeforeEach, the test binds a fresh random port per test method, multiplying the draws.

Fix directions

  1. Ephemeral port (preferred). Bind port 0 and read the actually assigned port back for the client connection. gRPC supports this (ServerBuilder.forPort(0) + Server#getPort()); if io.spine.server.Server does not expose the bound port, this needs a small API addition (or the test drops to GrpcContainer directly).
  2. Retry on BindException. Loop the random pick a few times on bind failure — smaller change, still probabilistic.
  3. Probe-then-bind (new ServerSocket(0) to discover a free port, close, bind gRPC to it) — better than blind random, still has a race window.

Acceptance

  • TestClientTest binds no fixed or blindly random port.
  • The Windows CI job passes repeatedly (e.g., 10 consecutive runs) without bind-related failures.
  • Any sibling tests in server-testlib binding fixed ports get the same treatment.

🤖 Generated with Claude Code

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

Status
📋 Backlog

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions