Tolerate Vert.x 5 pause/resume on ended requests (fixes silent test hangs)#5383
Merged
Conversation
Vert.x 5 changed HttpServerRequestImpl.pause()/fetch()/resume() semantics:
they now checkEnded() and throw IllegalStateException("Request has already
been read") once the request body END event has been delivered. In Vert.x 4
these calls were safe no-ops after end.
Tapir's vertx streaming bridge invokes pause()/resume() from fire-and-forget
fibers (fs2/zio back-pressure loops) and from Vert.x callbacks (Pipe drain
handlers, pipe start, streamPauseHandler). Under load, a pause/resume racing
the END event now throws, silently killing the back-pressure fiber or
skipping cleanup (e.g. socket.close()), which leaves stream consumers
waiting forever - the intermittent silent hangs seen in
CatsVertxServerTest / ZioVertxServerTest / WebSocket pipe tests on CI.
This restores the Vert.x 4 semantics at tapir's call sites by ignoring the
post-end IllegalStateException: after END there is nothing left to
pause/resume, so dropping the call is semantically correct.
Co-Authored-By: Claude Fable 5 <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 4 → 5 upgrade (80c5e37, 2026-06-10) changed
HttpServerRequestImpl.pause()/fetch()/resume()semantics: they nowcheckEnded()and throwIllegalStateException("Request has already been read")once the request body END event has been delivered. In Vert.x 4 these calls were safe no-ops after end.Tapir's vertx streaming bridge invokes
pause()/resume()from fire-and-forget fibers and Vert.x callbacks:server/vertx-server/cats/src/main/scala/sttp/tapir/server/vertx/cats/streams/fs2.scala— back-pressure loop in a started-and-never-joined fiberserver/vertx-server/zio/src/main/scala/sttp/tapir/server/vertx/zio/streams/zio.scala— same pattern,forkDaemon-edserver/vertx-server/src/main/scala/sttp/tapir/server/vertx/streams/Pipe.scala— drain handlers / pipe startserver/vertx-server/src/main/scala/sttp/tapir/server/vertx/handlers/package.scala—streamPauseHandlerfor every streaming/WS endpointUnder CI load, a
pause/resumeracing the END event now throws, silently killing the back-pressure fiber (or skipping cleanup such assocket.close()), leaving stream consumers waiting forever. This is the suspected root cause of the intermittent silent hangs in the vertx cats/zio suites seen on dependency PRs #5373 / #5362, where CI went quiet until the 15-minute retry timeout.Fix
Restore Vert.x 4 semantics at tapir's call sites by ignoring the post-end
IllegalStateException: after END there is nothing left to pause/resume, so dropping the call is semantically correct. No fibers/streams were restructured — only the pause/resume calls on the request/read-stream are guarded (.attempt.voidin fs2,.ignorein zio, a smallignoringReadEndedhelper inPipe.scala, and a try/catch instreamPauseHandler).Companion PR: #5382 adds defensive timeouts + a thread-dump watchdog on the CI side; this PR is the root-cause side.
Verification
All variants compile (2.12 / 2.13 / 3):
vertxServer{,2_12,3},vertxServerCats{,2_12,3},vertxServerZio{,2_12,3}.Test suites:
vertxServerCats2_12/test: Tests: succeeded 292, failed 0 — all passedvertxServerZio3/test: Tests: succeeded 295, failed 0 — all passedvertxServer/test(2.13): Tests: succeeded 554, failed 0 — all passedStress run of the formerly racy path —
vertxServerCats2_12/testOnly sttp.tapir.server.vertx.cats.CatsVertxServerTest5 times in a loop: 5/5 passed (285 tests each).🤖 Generated with Claude Code