bumptech/glide · error · IOException

Cannot reset to unset mark position

Error message

Cannot reset to unset mark position

What it means

Thrown by ByteBufferUtil.ByteBufferInputStream.reset() when reset() is called but no mark has been set (markPos == UNSET). This mirrors java.io.InputStream.reset() contract: you must mark() before you can reset(). The class tracks markPos itself because the framework ByteBuffer-backed reset() was buggy in Android 4.0.4.

Source

Thrown at library/src/main/java/com/bumptech/glide/util/ByteBufferUtil.java:310

    @Override
    public boolean markSupported() {
      return true;
    }

    @Override
    public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) {
      if (!byteBuffer.hasRemaining()) {
        return -1;
      }
      int toRead = Math.min(byteCount, available());
      byteBuffer.get(buffer, byteOffset, toRead);
      return toRead;
    }

    @Override
    public synchronized void reset() throws IOException {
      if (markPos == UNSET) {
        throw new IOException("Cannot reset to unset mark position");
      }
      // reset() was not implemented correctly in 4.0.4, so we track the mark position ourselves.
      byteBuffer.position(markPos);
    }

    @Override
    public long skip(long byteCount) {
      if (!byteBuffer.hasRemaining()) {
        return -1;
      }

      long toSkip = Math.min(byteCount, available());
      byteBuffer.position((int) (byteBuffer.position() + toSkip));
      return toSkip;
    }
  }
}

View on GitHub (pinned to eb14a895d8)

Solutions

  1. Call mark(readLimit) before reset() — for this stream the readLimit is ignored but mark() must be invoked
  2. Check markSupported() and markPos/isMarkSupported before reset() if you are writing a decoder
  3. Re-derive a fresh stream from the ByteBuffer (ByteBufferUtil.toStream(buffer.duplicate())) instead of resetting
  4. Catch IOException around reset and fall back to re-reading from the start of a duplicated buffer

Example fix

// before
val src = ByteBufferUtil.toStream(buf)
src.reset() // IOException: no mark set

// after
val src = ByteBufferUtil.toStream(buf)
src.mark(0)
// ... read ...
src.reset()
Defensive patterns

Strategy: validation

Validate before calling

val src = ByteBufferUtil.toStream(buf)
if (src.markSupported()) src.mark(0)
// ... read ...
if (src.markSupported()) src.reset()

Try / catch

try {
  stream.reset()
} catch (e: IOException) {
  // no mark set — re-derive a fresh stream from the buffer
  freshStream = ByteBufferUtil.toStream(buf.duplicate())
}

Prevention

When it happens

Trigger: Calling InputStream.reset() on a stream obtained from ByteBufferUtil.toStream(ByteBuffer) without a prior mark(); decoders or image formats that attempt reset-based rewinding without first checking markSupported()/mark().

Common situations: Custom BitmapDecoder/ResourceDecoder that rewinds the stream; image format libraries (GIF, WebP) that probe the stream then reset; copying ByteBufferUtil-provided stream into another lib that calls reset() defensively.

Related errors


AI-assisted analysis of bumptech/glide@eb14a895d8 (2026-08-14). Data as JSON: /api/errors/22f170cbe2ad4023. Report an issue: GitHub.