{"record":{"id":"11b6acc391407164","repo":"didi/DoKit","slug":"closed","errorCode":null,"errorMessage":"closed","messagePattern":"closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"Android/dokit-okhttp-v3/src/main/java/com/didichuxing/doraemonkit/okhttp_api/ByteCountBufferedSinkV3.java","lineNumber":46,"sourceCode":"        this.mOriginalSink = sink;\n        this.mDelegate = Okio.buffer(mOriginalSink);\n        this.mByteCount = byteCount;\n    }\n\n    @Override\n    public long writeAll(Source source) throws IOException {\n        if (source == null) throw new IllegalArgumentException(\"source == null\");\n        long totalBytesRead = 0;\n        for (long readCount; (readCount = source.read(buffer(), mByteCount)) != -1; ) {\n            totalBytesRead += readCount;\n            emitCompleteSegments();\n        }\n        return totalBytesRead;\n    }\n\n    @Override\n    public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {\n        if (!isOpen()) throw new IllegalStateException(\"closed\");\n        //计算出要写入的次数\n        long count = (long) Math.ceil((double) source.length / mByteCount);\n        for (int i = 0; i < count; i++) {\n            //让每次写入的字节数精确到mByteCount 分多次写入\n            long newOffset = i * mByteCount;\n            long writeByteCount = Math.min(mByteCount, source.length - newOffset);\n            buffer().write(source, (int) newOffset, (int) writeByteCount);\n            emitCompleteSegments();\n        }\n        return this;\n    }\n\n    @Override\n    public BufferedSink emitCompleteSegments() throws IOException {\n        final Buffer buffer = buffer();\n        mOriginalSink.write(buffer, buffer.size());\n        return this;\n    }","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/didi/DoKit/blob/626827cddb2feb2f3aee87a52a064b4e5ca2bed4/Android/dokit-okhttp-v3/src/main/java/com/didichuxing/doraemonkit/okhttp_api/ByteCountBufferedSinkV3.java#L28-L64","documentation":"ByteCountBufferedSinkV3.write(byte[], int, int) checks isOpen() before writing; once close() has been called (delegating to the underlying buffered sink), isOpen() returns false and any further byte-array write throws IllegalStateException('closed'). This matches okio's BufferedSink contract that a closed sink rejects writes.","triggerScenarios":"Calling write(byte[], offset, byteCount) after close() — e.g. an interceptor that copies a body to both the network and DoraomonKit's capture sink closes the sink on one path, then a retry/redirect path writes again. Also closing in a finally block but continuing to use the sink on success paths.","commonSituations":"OkHttp interceptor chains with retries/redirects re-invoking body copying. Exception paths where close() runs early (try-with-resources scoping too wide) and subsequent code still writes. Sharing one capture sink across multiple requests.","solutions":["Track lifecycle: only close the sink after all writes are complete (close in the interceptor's finally after the response is fully consumed)","Check isOpen() before writing and skip/recreate the sink if closed","Create a new ByteCountBufferedSinkV3 per request/response instead of reusing a closed instance"],"exampleFix":"// before\ntry { byteCountSink.write(body, 0, body.length); }\nfinally { byteCountSink.close(); }\n// later, on a retry path:\nbyteCountSink.write(body, 0, body.length); // IllegalStateException\n\n// after\nif (byteCountSink.isOpen()) {\n  byteCountSink.write(body, 0, body.length);\n} else {\n  byteCountSink = new ByteCountBufferedSinkV3(sink, CHUNK);\n  byteCountSink.write(body, 0, body.length);\n}","handlingStrategy":"validation","validationCode":"if (byteCountSink.isOpen()) { byteCountSink.write(source, 0, source.length); }","typeGuard":null,"tryCatchPattern":"try { sink.write(bytes, 0, bytes.length); } catch (IllegalStateException e) { if (\"closed\".equals(e.getMessage())) { /* recreate sink or skip: body already flushed */ } else throw e; }","preventionTips":["Close the sink only after all writes complete (finally after full body consumption)","Use one sink instance per request/response; never reuse across retries"],"tags":["okio","okhttp","doraemonkit","lifecycle","java"],"backgroundTag":null,"analyzedSha":"626827cddb2feb2f3aee87a52a064b4e5ca2bed4","analyzedAt":"2026-08-14T12:45:58.758Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}