{"record":{"id":"89d03ba1629cec7f","repo":"apache/dubbo","slug":"stream-closed","errorCode":null,"errorMessage":"Stream closed","messagePattern":"Stream closed","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"dubbo-common/src/main/java/org/apache/dubbo/common/io/UnsafeStringReader.java","lineNumber":114,"sourceCode":"\n        ensureOpen();\n        mMark = mPosition;\n    }\n\n    @Override\n    public void reset() throws IOException {\n        ensureOpen();\n        mPosition = mMark;\n    }\n\n    @Override\n    public void close() throws IOException {\n        mString = null;\n    }\n\n    private void ensureOpen() throws IOException {\n        if (mString == null) {\n            throw new IOException(\"Stream closed\");\n        }\n    }\n}\n","sourceCodeStart":96,"sourceCodeEnd":118,"githubUrl":"https://github.com/apache/dubbo/blob/3a3043227f5571d25eb2889de5bca22f2914843b/dubbo-common/src/main/java/org/apache/dubbo/common/io/UnsafeStringReader.java#L96-L118","documentation":"Thrown by UnsafeStringReader.ensureOpen() (a private guard invoked by read, read(char[],int,int), skip, ready, reset, and mark) when mString == null, i.e. after close() has been called. close() sets mString = null as its sole action, so every subsequent read-family operation trips this IOException. It is the standard 'use after close' signal matching java.io.StringReader.","triggerScenarios":"Any sequence: reader.close(); then reader.read(), reader.skip(n), reader.ready(), reader.reset(), or reader.mark(n) on the same UnsafeStringReader. Also reached if close() is called twice and the second close somehow routes through ensureOpen (it does not — close() itself never calls ensureOpen, so only post-close read operations trigger it).","commonSituations":"Try-with-resources block where code uses the reader after the try exits; sharing a reader across components where one calls close() and another reads; reusing a cached reader field that was closed by a previous request; finalizer/cleanup ordering bugs.","solutions":["Do not use the reader after close() — treat close() as terminal. If using try-with-resources, move all reads inside the try block.","Ensure single ownership: only the component that opens the reader closes it.","If a reader field may be reused, null it out after close and create a fresh UnsafeStringReader per use.","Catch IOException at the call site and treat 'Stream closed' as end-of-lifecycle rather than retrying."],"exampleFix":"// before\ntry (UnsafeStringReader r = new UnsafeStringReader(s)) {\n    parse(r);\n}\nint c = r.read(); // closed -> IOException\n// after\ntry (UnsafeStringReader r = new UnsafeStringReader(s)) {\n    parse(r);\n    int c = r.read(); // read inside the scope\n}","handlingStrategy":"try-catch","validationCode":"// Check whether the reader is still open by attempting ready() inside try-with-resources scope.\n// There is no public isOpen(); the only signal is the IOException itself.","typeGuard":null,"tryCatchPattern":"try {\n    int c = reader.read();\n} catch (IOException e) {\n    if (\"Stream closed\".equals(e.getMessage())) {\n        // reader already closed; treat as end-of-lifecycle, do not reuse\n        return;\n    }\n    throw e;\n}","preventionTips":["Use try-with-resources and keep all reads inside the try block.","Make a single component own the reader's lifecycle (the opener closes it).","After close(), null out the reader field so accidental reuse fails fast at a null check.","Treat 'Stream closed' as a terminal condition — never retry the read on the same instance."],"tags":["io","string-reader","resource-lifecycle","use-after-close","dubbo-common"],"backgroundTag":null,"analyzedSha":"3a3043227f5571d25eb2889de5bca22f2914843b","analyzedAt":"2026-08-14T00:43:19.853Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}