didi/DoKit · error · IllegalStateException
Unable to mark: <e>
Error message
Unable to mark: <e>
What it means
MarkableInputStream.setLimit() throws IllegalStateException when the wrapped stream's mark()/reset() calls fail with an IOException. Picasso wraps network streams in MarkableInputStream so the same bytes can be re-read (e.g. to decode bounds first); when the underlying stream cannot honor marking, this wrapping error surfaces as a runtime exception.
Source
Thrown at Android/dokit/src/main/java/com/didichuxing/doraemonkit/picasso/MarkableInputStream.java:87
* {@code reset} thru {@code limit}. Since we can't call {@code mark()}
* without also adjusting the reset-to-position on the underlying stream this
* method resets first and then marks the union of the two byte ranges. On
* buffered streams this additional cursor motion shouldn't result in any
* additional I/O.
*/
private void setLimit(long limit) {
try {
if (reset < offset && offset <= this.limit) {
in.reset();
in.mark((int) (limit - reset));
skip(reset, offset);
} else {
reset = offset;
in.mark((int) (limit - offset));
}
this.limit = limit;
} catch (IOException e) {
throw new IllegalStateException("Unable to mark: " + e);
}
}
/** Resets the stream to the most recent {@link #mark mark}. */
@Override public void reset() throws IOException {
reset(defaultMark);
}
/** Resets the stream to the position recorded by {@code token}. */
public void reset(long token) throws IOException {
if (offset > limit || token < reset) {
throw new IOException("Cannot reset");
}
in.reset();
skip(reset, token);
offset = token;
}
View on GitHub (pinned to 626827cddb)
Solutions
- Return a stream that supports marking with a sufficient readlimit from the Downloader (e.g. wrap in BufferedInputStream)
- Ensure the stream is fresh (not already consumed) when handed to Picasso
- If wrapping an exotic stream, subclass or pre-buffer it so mark/reset work
Example fix
// before return new Response(rawSocketStream, false, len); // mark/reset fail downstream // after return new Response(new BufferedInputStream(rawSocketStream, 64 * 1024), false, len);
Defensive patterns
Strategy: fallback
Validate before calling
InputStream wrapped = rawStream.markSupported() ? rawStream : new BufferedInputStream(rawStream, 64 * 1024); return new Response(wrapped, false, len);
Type guard
boolean isMarkSafe(InputStream in) { return in.markSupported(); } Try / catch
try { return new Response(raw, false, len); } catch (IllegalStateException e) { if (e.getMessage() != null && e.getMessage().contains("Unable to mark")) { return new Response(new BufferedInputStream(raw, 64 * 1024), false, len); } throw e; } Prevention
- Always hand Picasso buffered streams that support mark/reset
- Do not consume the stream before returning it from a Downloader
When it happens
Trigger: The wrapped InputStream throwing IOException inside markSupported-limited streams, or reset() failing after the mark window expired, during savePosition(0)/setLimit operations in the stream decode path.
Common situations: Custom Downloaders returning streams with small or broken mark limits; streams already consumed before being wrapped; okhttp response streams after the connection was closed or intercepted oddly.
Related errors
AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14).
Data as JSON: /api/errors/72a66dd09c73937e.
Report an issue: GitHub.