{"record":{"id":"1390e0a286fc62ce","repo":"apache/incubator-seata","slug":"stream-closed-unexpectedly","errorCode":null,"errorMessage":"Stream closed unexpectedly","messagePattern":"Stream closed unexpectedly","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"common/src/main/java/org/apache/seata/common/util/SeataHttpWatch.java","lineNumber":165,"sourceCode":"            // Check if source is exhausted (stream closed)\n            return !source.exhausted();\n        } catch (IOException e) {\n            LOGGER.error(\"Error checking if stream has more data\", e);\n            return false;\n        }\n    }\n\n    @Override\n    public Response<T> next() {\n        try {\n            /*\n            Read a single line and parse it as an event.\n            Format: \"{prefix}{json}\\n\" where prefix is defined in Constants.WATCH_EVENT_PREFIX.\n            Each line is a complete event, event type is included in the JSON data.\n            */\n            String line = source.readUtf8Line();\n            if (line == null) {\n                throw new RuntimeException(\"Stream closed unexpectedly\");\n            }\n\n            if (!line.startsWith(Constants.WATCH_EVENT_PREFIX)) {\n                throw new RuntimeException(\"Invalid event format: expected prefix '\" + Constants.WATCH_EVENT_PREFIX\n                        + \"', got: \" + (line.length() > 20 ? line.substring(0, 20) + \"...\" : line));\n            }\n\n            String jsonData = line.substring(Constants.WATCH_EVENT_PREFIX.length());\n            return parseEvent(jsonData);\n\n        } catch (IOException e) {\n            throw new RuntimeException(\"IO Exception during next()\", e);\n        }\n    }\n\n    /**\n     * Parse event JSON into Response object.\n     * Simplified format: only contains group, timestamp, and metadata fields.","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/apache/incubator-seata/blob/e01f97c6db397165050caa6764020410c2c8199a/common/src/main/java/org/apache/seata/common/util/SeataHttpWatch.java#L147-L183","documentation":"Thrown from SeataHttpWatch.next() when the underlying BufferedSource.readUtf8Line() returns null, meaning the server closed the event stream (EOF) while the client was still iterating events. Because the watch protocol expects an unbounded stream of prefixed lines, EOF is treated as an unexpected termination rather than normal completion.","triggerScenarios":"Iterating watch.next() and the server closes the connection: server shutdown/restart, idle-timeout enforced by a proxy or load balancer, network drop, or the server deliberately ending the stream after an error event.","commonSituations":"Long-lived watch connections killed by LB idle timeouts (default nginx 60s read timeout with no keepalive events); Seata server rolling deployment; flaky network between client and server; watchdog code that assumes the stream never ends.","solutions":["Treat this as a signal to reconnect: create a new watch via createWatch and resume iteration.","If a proxy/load balancer terminates idle SSE connections, raise its read timeout or enable keepalive heartbeats on the server side.","Check whether the Seata server restarted (server logs) at the moment of the failure.","Pin down network instability between client and server if restarts are not the cause."],"exampleFix":"// before\nwhile (true) {\n    Response<MyEvent> ev = watch.next();\n    handle(ev);\n}\n\n// after: reconnect on stream end\nwhile (running) {\n    try (SeataHttpWatch<MyEvent> w = SeataHttpWatch.createWatch(call.clone(), MyEvent.class)) {\n        while (true) {\n            handle(w.next());\n        }\n    } catch (RuntimeException e) {\n        // 'Stream closed unexpectedly' or IO failure -> reconnect with backoff\n        sleepBackoff();\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (RuntimeException e) {\n    if (\"Stream closed unexpectedly\".equals(e.getMessage())) {\n        watch = SeataHttpWatch.createWatch(call.clone(), eventType); // reconnect\n        continue;\n    }\n    throw e;\n}","preventionTips":["Assume watch streams end; always code a reconnect loop with backoff and jitter.","call.clone() lets you re-issue the same request after the original Call was consumed.","Configure intermediary idle timeouts to exceed the longest expected quiet period on the stream."],"tags":["network","watch","sse","reconnect","eof"],"backgroundTag":null,"analyzedSha":"e01f97c6db397165050caa6764020410c2c8199a","analyzedAt":"2026-08-14T10:23:53.097Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}