{"record":{"id":"77f7ae4ef1c0b3cb","repo":"quarkusio/quarkus","slug":"already-closed","errorCode":null,"errorMessage":"Already closed","messagePattern":"Already closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/SseEventSinkImpl.java","lineNumber":34,"sourceCode":"\n    public static final byte[] EMPTY_BUFFER = new byte[0];\n    private ResteasyReactiveRequestContext context;\n    private SseBroadcasterImpl broadcaster;\n    private AtomicBoolean closed = new AtomicBoolean();\n\n    public SseEventSinkImpl(ResteasyReactiveRequestContext context) {\n        this.context = context;\n    }\n\n    @Override\n    public boolean isClosed() {\n        return context.serverResponse().closed() || closed.get();\n    }\n\n    @Override\n    public CompletionStage<?> send(OutboundSseEvent event) {\n        if (isClosed())\n            throw new IllegalStateException(\"Already closed\");\n        // NOTE: we can't cast event to OutboundSseEventImpl because the TCK sends us its own subclass\n        return SseUtil.send(context, event, Collections.emptyList());\n    }\n\n    @Override\n    public void close() {\n        if (!closed.compareAndSet(false, true))\n            return;\n\n        ServerHttpResponse response = context.serverResponse();\n        if (!response.closed()) {\n            if (!response.headWritten()) {\n                // make sure we send the headers if we're closing this sink before the\n                // endpoint method is over\n                SseUtil.setHeaders(context, response);\n            }\n            response.end();\n            context.close();","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/jaxrs/SseEventSinkImpl.java#L16-L52","documentation":"SseEventSinkImpl.send() throws IllegalStateException when the sink is already closed — either explicitly via close() or because the underlying server response connection was closed by the client. No further events can be written to that sink.","triggerScenarios":"Calling sink.send(event) after sink.close(), after the client disconnected (context.serverResponse().closed()), or in a scheduled/async task that outlives the connection.","commonSituations":"Periodic jobs pushing to a stale sink; broadcasting to a list of sinks where some clients left; client browser tab closed while server keeps streaming; long GC pause causing connection termination.","solutions":["Check sink.isClosed() before each send, and prune closed sinks from your registry.","Catch IllegalStateException around send() and remove the sink from active tracking.","Register an onClose handler on the broadcaster to drop dead sinks promptly.","Use SseBroadcaster (which handles closed sinks internally) instead of manual per-sink sends."],"exampleFix":"// before\nsink.send(event); // IllegalStateException if client gone\n// after\nif (!sink.isClosed()) {\n    sink.send(event);\n} else {\n    sinks.remove(sink);\n}","handlingStrategy":"type-guard","validationCode":"if (sink.isClosed()) {\n    sinks.remove(sink);\n    return;\n}","typeGuard":"boolean canSend(SseEventSink s) {\n    return s instanceof SseEventSinkImpl impl && !impl.isClosed();\n}","tryCatchPattern":"try {\n    sink.send(event);\n} catch (IllegalStateException e) {\n    sinks.remove(sink); // client disconnected\n}","preventionTips":["Check isClosed() before every send","Register onClose handlers to prune dead sinks","Prefer SseBroadcaster over manual sends for multi-client fan-out","Bound scheduled tasks to the connection lifetime"],"tags":["jaxrs","sse","resteasy-reactive","illegal-state"],"backgroundTag":"sse-sink-closed","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"}