quarkusio/quarkus · error · IOException
Connection has been closed
Error message
Connection has been closed
What it means
While waiting for the response write queue to drain, awaitWriteable() checks response.closed(); if Vertx reports the response/connection has closed, it throws IOException('Connection has been closed') because the queued data can never be delivered. This prevents blocking indefinitely on a drain event that will never arrive.
Source
Thrown at independent-projects/vertx-utils/src/main/java/io/quarkus/vertx/utils/VertxOutputStream.java:128
throw new IOException("Failed to write", e);
} finally {
lock.unlock();
}
}
private void awaitWriteable() throws IOException {
// is it running in an event loop?
if (Context.isOnEventLoopThread()) {
// NEVER block the event loop!
return;
}
assert lock.isHeldByCurrentThread();
while (response.writeQueueFull()) {
if (throwable != null) {
throw new IOException(throwable);
}
if (response.closed()) {
throw new IOException("Connection has been closed");
}
try {
waitingForDrain = true;
// To make sure we don't get stuck waiting, we time out periodically
drainCondition.await(1, TimeUnit.SECONDS);
// Check for timeout / closed connection
if (request.response().ended() || request.response().closed()) {
if (throwable != null) {
// We had an exception, propagate it
throw new IOException(throwable);
} else {
throw new IOException("Connection has been closed");
}
}
} catch (InterruptedException e) {
throw new InterruptedIOException(e.getMessage());
} finally {
waitingForDrain = false;View on GitHub (pinned to e1c734241f)
Solutions
- Treat this as a normal client-disconnect signal: catch it, release resources, and stop producing data
- Tune server idle/timeouts if legitimate slow clients are being cut off
- Write smaller chunks more frequently so the queue drains between writes
- Add a drain/close handler on the response to abort generation work early
Example fix
// before
while (hasMore()) {
out.write(nextChunk()); // throws once client disconnects
}
// after
try {
while (hasMore() && !response.closed()) {
out.write(nextChunk());
}
} catch (IOException closed) {
cancelGeneration();
} Defensive patterns
Strategy: try-catch
Try / catch
try {
out.writeBlocking(chunk);
} catch (IOException e) {
if (e.getCause() != null) {
logger.warn("Connection failure during stream: {}", e.getCause().getMessage());
}
cleanup();
} Prevention
- Detect client disconnects early (response.closeHandler) and cancel generation
- Don't end the response from another thread while streaming is in progress
- Set realistic quarkus.http.idle-timeout values for long streams
When it happens
Trigger: write() is called, the write queue is full, and the peer closes the TCP connection (or the response is closed server-side) before the queue drains.
Common situations: User cancels a download; mobile clients switching networks mid-stream; reverse proxy closing upstream connections after idle timeout; server-side timeouts (e.g. quarkus.http.idle-timeout) firing during a slow stream.
Related errors
- Failed to write
- Stream is closed
- Failed to write
- Connection has been closed
- Failed to open path tree with root %s
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/7240fa01b3986d15.
Report an issue: GitHub.