{"record":{"id":"1d8438a220082336","repo":"quarkusio/quarkus","slug":"connection-has-been-closed","errorCode":null,"errorMessage":"Connection has been closed","messagePattern":"Connection has been closed","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"warning","filePath":"extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxBlockingOutput.java","lineNumber":153,"sourceCode":"                ret.completeExceptionally(res.cause());\n        };\n    }\n\n    private void awaitWriteable() throws IOException {\n        if (first) {\n            first = false;\n            return;\n        }\n        assert Thread.holdsLock(request.connection());\n        while (request.response().writeQueueFull()) {\n            if (throwable != null) {\n                throw new IOException(throwable);\n            }\n            if (Context.isOnEventLoopThread()) {\n                throw new BlockingOperationNotAllowedException(\"Attempting a blocking write on io thread\");\n            }\n            if (request.response().closed()) {\n                throw new IOException(\"Connection has been closed\");\n            }\n            if (!drainHandlerRegistered) {\n                drainHandlerRegistered = true;\n                Handler<Void> handler = new Handler<Void>() {\n                    @Override\n                    public void handle(Void event) {\n                        if (waitingForDrain) {\n                            HttpConnection connection = request.connection();\n                            synchronized (connection) {\n                                connection.notifyAll();\n                            }\n                        }\n                    }\n                };\n                request.response().drainHandler(handler);\n                request.response().closeHandler(handler);\n            }\n            try {","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/resteasy-classic/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxBlockingOutput.java#L135-L171","documentation":"While waiting for the Vert.x response write queue to drain, VertxBlockingOutput checks request.response().closed(); if the client's connection is already closed it throws IOException(\"Connection has been closed\") because no further bytes can be delivered. This is a client-disconnect detection during blocking output.","triggerScenarios":"Blocking write loop in awaitWriteable() observes the Vert.x HttpServerResponse is closed: client closed the socket/tab, TCP reset, load balancer killed an idle/slow connection while the server was still streaming the response.","commonSituations":"Long-running SSE or streamed downloads interrupted by the client; user cancels a browser request; mobile client loses network mid-response; reverse-proxy timeouts on slow upstreams.","solutions":["Handle it as a normal termination: catch IOException around the streaming code and stop producing data (do not retry).","Detect client disconnects early (response closeHandler) and cancel the upstream generation task to avoid wasted work.","Reduce time-to-first-byte and response duration so clients/proxies don't time out; adjust LB/proxy idle timeouts if legitimately slow."],"exampleFix":"// stream defensively\ntry {\n    while (hasData()) {\n        out.write(chunk);\n    }\n} catch (IOException e) {\n    LOG.debugf(\"Client went away while streaming: %s\", e.getMessage());\n    cancelUpstream(); // stop generating data\n}","handlingStrategy":"try-catch","validationCode":"if (request.response().closed()) {\n    cancelUpstream();\n    return; // nothing can be delivered\n}","typeGuard":"boolean writable(io.vertx.core.http.HttpServerResponse resp) {\n    return resp != null && !resp.closed() && !resp.headWritten() || !resp.closed();\n}","tryCatchPattern":"try {\n    streamAll(out);\n} catch (IOException e) {\n    if (\"Connection has been closed\".equals(e.getMessage())) {\n        LOG.debug(\"Client disconnected; aborting stream\");\n        cancelUpstream();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Register a close/drain handler and cancel generation tasks on client disconnect.","Avoid very long-lived responses; paginate or chunk data.","Tune load balancer and proxy idle timeouts to exceed worst-case response time.","Treat disconnect IOExceptions as normal and log at debug level."],"tags":["io","network","client-disconnect","vertx"],"backgroundTag":"connection-closed-mid-response","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}