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

  1. Return a stream that supports marking with a sufficient readlimit from the Downloader (e.g. wrap in BufferedInputStream)
  2. Ensure the stream is fresh (not already consumed) when handed to Picasso
  3. 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

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.