Skip to content

Retry flaky Vert.x WebSocket server tests#5410

Merged
adamw merged 2 commits into
masterfrom
test/retry-flaky-vertx-ws-tests
Jul 13, 2026
Merged

Retry flaky Vert.x WebSocket server tests#5410
adamw merged 2 commits into
masterfrom
test/retry-flaky-vertx-ws-tests

Conversation

@adamw

@adamw adamw commented Jul 13, 2026

Copy link
Copy Markdown
Member

Problem

The Vert.x WebSocket server tests (CatsVertxServerTest, ZioVertxServerTest, VertxServerTest) intermittently fail CI with a 3-minute TimeoutException — e.g. recently on scala-steward dependency PRs (#5404), unrelated to the bumped dependency.

Root cause (upstream Vert.x, not tapir)

A race in HttpServerRequest.toWebSocket(): Vert.x writes+flushes the HTTP 101 in one event-loop task and completes the returned Future (where we register our frame handler) in a later task. A client frame arriving in that ~1-3 ms window is read and then silently droppedWebSocketImplBase.receiveFrame discards TEXT/BINARY frames when no handler is registered, and the server WebSocket's inbound queue starts unpaused. The echo never returns, so the test times out.

This is not fixable via Vert.x's public per-request API: toWebSocket() hands back an already-flowing socket, and the intermediate handshake object isn't exposed (its pause() throws UnsupportedOperationException). The only race-free API is the server-level HttpServer.webSocketHandler, which bypasses tapir's Router. The bug is present in every released Vert.x and on current master; it will be reported upstream (the fix there is to start the server WebSocket paused / buffer inbound frames until a handler is registered).

This change

Interim, test-only: retry failed Vert.x server tests using the same withFixture retry already used by JdkHttpServerTest and ZioHttpServerTest. retries = 3 (lower than those suites' 5, because a WS flake hangs ~3 min per attempt rather than failing fast). Each attempt re-runs with a fresh timeout budget. No production code changes.

🤖 Generated with Claude Code

adamw and others added 2 commits July 13, 2026 09:39
The Vert.x WebSocket server tests intermittently time out on CI due to an
upstream Vert.x race in `HttpServerRequest.toWebSocket()`: a client frame sent
in the brief window after the 101 handshake but before Vert.x completes the
upgrade (so before we can register a frame handler) is silently dropped
(`WebSocketImplBase.receiveFrame` discards frames when no handler is set, and the
server WebSocket starts unpaused). The echo then never returns and the test hits
its 3-minute timeout.

This is not fixable via Vert.x's public per-request API (`toWebSocket()` returns
an already-flowing socket; the handshake object is not exposed), and is present
in every released Vert.x and on current master; to be reported upstream. As an
interim, retry failed tests using the same `withFixture` mechanism already used
by JdkHttpServerTest and ZioHttpServerTest. `retries = 3` (lower than the 5 those
suites use, since a WS flake hangs ~3 min per attempt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The withFixture retry-on-failure block was copy-pasted across five suites
(JdkHttpServerTest, ZioHttpServerTest, and the three Vert.x server suites).
Move it into TestSuite behind an overridable `retries` def (default 0 = no
retries, so all other suites are unchanged). Each flaky suite now only sets
`override def retries = N` with its rationale comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@adamw adamw merged commit ca1f60f into master Jul 13, 2026
22 checks passed
@adamw adamw deleted the test/retry-flaky-vertx-ws-tests branch July 13, 2026 11:26
adamw added a commit that referenced this pull request Jul 13, 2026
…orks (#5411)

## Problem

The flaky-WebSocket-test retry added in #5410 (`TestSuite.retries`) did
**not** actually rescue the Vert.x WS flake. On CI the retry ran but
every attempt after the first failed **instantly** with:

```
ERROR DefaultCreateServerTest - Starting server failed because of null
java.lang.IllegalStateException
    at io.vertx.core.http.impl.CleanableHttpServer.listen(CleanableHttpServer.java:61)
```

## Root cause

The three Vert.x **test** interpreters build the HTTP server **eagerly,
outside** the `Resource.make` acquire:

```scala
val server = vertx.createHttpServer(...).requestHandler(router)  // captured once
val listenIO = vertxFutureToIo(server.listen(0))
Resource.make(listenIO)(...)
```

The retry re-runs the whole test, re-using the same server `Resource`,
which calls `listen()` a **second time on the same server instance**.
Vert.x 5's `CleanableHttpServer.listen` throws `IllegalStateException`
(empty message → logged as "because of null") when its `listenContext`
is already set. So the per-test server Resource simply wasn't repeatable
— the shared Vert.x was never actually broken.

## Fix

Create the server **inside** the acquire, so each acquire (and each
retry) gets a fresh server:

```scala
val listenIO = IO.delay {
  val router = Router.router(vertx)
  val _ = route(router)
  vertx.createHttpServer(...).requestHandler(router)
}.flatMap(server => vertxFutureToIo(server.listen(0)))
Resource.make(listenIO)(s => vertxFutureToIo(s.close()).void).map(_.actualPort())
```

Applied to `VertxTestServerInterpreter`,
`CatsVertxTestServerInterpreter`, `ZioVertxTestServerInterpreter`.

## Test

`ServerResourceRepeatableSpec` acquires the server Resource twice (what
the retry does). It throws the exact `IllegalStateException` on the old
eager code and passes with this fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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