{"record":{"id":"db711de78c2988f5","repo":"Tencent/tinker","slug":"stream-is-closed-db711d","errorCode":null,"errorMessage":"Stream is closed","messagePattern":"Stream is closed","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipOutputStream.java","lineNumber":375,"sourceCode":"    /*public void setLevel(int level) {\n        if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) {\n            throw new IllegalArgumentException(\"Bad level: \" + level);\n        }\n        compressionLevel = level;\n    }*/\n\n    /**\n     * Indicates that all entries have been written to the stream. Any terminal\n     * information is written to the underlying stream.\n     *\n     * @throws IOException\n     *             if an error occurs while terminating the stream.\n     */\n    // @Override\n    public void finish() throws IOException {\n        // TODO: is there a bug here? why not checkOpen?\n        if (out == null) {\n            throw new IOException(\"Stream is closed\");\n        }\n        if (cDir == null) {\n            return;\n        }\n        if (entries.isEmpty()) {\n            throw new ZipException(\"No entries\");\n        }\n        if (currentEntry != null) {\n            closeEntry();\n        }\n        int cdirEntriesSize = cDir.size();\n        /*if (archiveNeedsZip64EocdRecord) {\n            Zip64.writeZip64EocdRecordAndLocator(cDir, entries.size(), offset, cdirEntriesSize);\n        }*/\n        // Write Central Dir End\n        writeLongAsUint32(cDir, ENDSIG);\n        writeIntAsUint16(cDir, 0); // Disk Number\n        writeIntAsUint16(cDir, 0); // Start Disk","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/Tencent/tinker/blob/1b7ea02c239840f563ea64fb5bd286eb98d4011e/third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipOutputStream.java#L357-L393","documentation":"TinkerZipOutputStream.finish() writes the central directory and EOCD to terminate the archive. It first checks that the underlying stream reference (out) is non-null; close() nulls it, so calling finish() (directly or via close()'s internal path in the wrong order) after the stream is closed throws IOException('Stream is closed'). The source even carries a TODO questioning whether checkOpen should be used; the observable contract is that finish on a closed stream fails with this message rather than being a no-op.","triggerScenarios":"Calling finish() after close(); or double-finishing via a wrapper that also closes (e.g. calling finish() manually inside a try block whose try-with-resources then invokes close() again after an exception path already closed the stream).","commonSituations":"Manual finish()+close() pairs plus an auto-close scope; finally blocks that attempt finish() to salvage a partial archive after an error; reusing a stream object across write attempts.","solutions":["Call finish() exactly once, before close(), and let close() be the only teardown (finish+close or just close — never finish after close).","If using try-with-resources, drop manual finish()/close() calls entirely.","In error-recovery paths, guard the salvage finish with a flag so it cannot run after the stream was closed."],"exampleFix":"// before\nTinkerZipOutputStream zos = new TinkerZipOutputStream(out);\ntry {\n    writeEntries(zos);\n} finally {\n    zos.close();\n}\nzos.finish(); // IOException: Stream is closed\n\n// after\ntry (TinkerZipOutputStream zos = new TinkerZipOutputStream(out)) {\n    writeEntries(zos);\n    zos.finish();\n}","handlingStrategy":"validation","validationCode":"boolean finished = false;\n// ... write entries ...\nif (!finished) {\n    zos.finish();\n    finished = true;\n}\nzos.close();","typeGuard":null,"tryCatchPattern":"try {\n    zos.finish();\n} catch (IOException e) {\n    if (\"Stream is closed\".equals(e.getMessage())) {\n        // already terminated; nothing to salvage — treat as a lifecycle bug\n        log.warn(\"finish() called after close(); fix the call order\", e);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Prefer try-with-resources and let close() terminate the archive; add finish() only when you must keep the underlying stream open.","Never call finish() after close(); track a finished flag if control flow is complex.","One stream, one lifecycle: do not reuse TinkerZipOutputStream objects across attempts."],"tags":["zip","lifecycle","stream","tinker"],"backgroundTag":null,"analyzedSha":"1b7ea02c239840f563ea64fb5bd286eb98d4011e","analyzedAt":"2026-08-14T15:16:52.110Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}