didi/DoKit · error · IllegalStateException
closed
Error message
closed
What it means
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.
Source
Thrown at Android/dokit-okhttp-v3/src/main/java/com/didichuxing/doraemonkit/okhttp_api/ByteCountBufferedSinkV3.java:46
this.mOriginalSink = sink;
this.mDelegate = Okio.buffer(mOriginalSink);
this.mByteCount = byteCount;
}
@Override
public long writeAll(Source source) throws IOException {
if (source == null) throw new IllegalArgumentException("source == null");
long totalBytesRead = 0;
for (long readCount; (readCount = source.read(buffer(), mByteCount)) != -1; ) {
totalBytesRead += readCount;
emitCompleteSegments();
}
return totalBytesRead;
}
@Override
public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {
if (!isOpen()) throw new IllegalStateException("closed");
//计算出要写入的次数
long count = (long) Math.ceil((double) source.length / mByteCount);
for (int i = 0; i < count; i++) {
//让每次写入的字节数精确到mByteCount 分多次写入
long newOffset = i * mByteCount;
long writeByteCount = Math.min(mByteCount, source.length - newOffset);
buffer().write(source, (int) newOffset, (int) writeByteCount);
emitCompleteSegments();
}
return this;
}
@Override
public BufferedSink emitCompleteSegments() throws IOException {
final Buffer buffer = buffer();
mOriginalSink.write(buffer, buffer.size());
return this;
}View on GitHub (pinned to 626827cddb)
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
Example fix
// before
try { byteCountSink.write(body, 0, body.length); }
finally { byteCountSink.close(); }
// later, on a retry path:
byteCountSink.write(body, 0, body.length); // IllegalStateException
// after
if (byteCountSink.isOpen()) {
byteCountSink.write(body, 0, body.length);
} else {
byteCountSink = new ByteCountBufferedSinkV3(sink, CHUNK);
byteCountSink.write(body, 0, body.length);
} Defensive patterns
Strategy: validation
Validate before calling
if (byteCountSink.isOpen()) { byteCountSink.write(source, 0, source.length); } Try / catch
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; } Prevention
- Close the sink only after all writes complete (finally after full body consumption)
- Use one sink instance per request/response; never reuse across retries
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- source == null
- source == null
- Index: " + index + ", array1 Length: 0
- Index: " + index + ", array1 Length: " + len1
- Index: " + index + ", Length: 0
AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14).
Data as JSON: /api/errors/11b6acc391407164.
Report an issue: GitHub.