Retry flaky Vert.x WebSocket server tests#5410
Merged
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Vert.x WebSocket server tests (
CatsVertxServerTest,ZioVertxServerTest,VertxServerTest) intermittently fail CI with a 3-minuteTimeoutException— 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 returnedFuture(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 dropped —WebSocketImplBase.receiveFramediscards 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 (itspause()throwsUnsupportedOperationException). The only race-free API is the server-levelHttpServer.webSocketHandler, which bypasses tapir'sRouter. The bug is present in every released Vert.x and on currentmaster; 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
withFixtureretry already used byJdkHttpServerTestandZioHttpServerTest.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