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
- Call mark(readLimit) before reset() — for this stream the readLimit is ignored but mark() must be invoked
- Check markSupported() and markPos/isMarkSupported before reset() if you are writing a decoder
- Re-derive a fresh stream from the ByteBuffer (ByteBufferUtil.toStream(buffer.duplicate())) instead of resetting
- 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
- Always call mark() before reset() on streams obtained from ByteBufferUtil
- For repeated reads, duplicate the ByteBuffer and create a fresh stream instead of resetting
- When writing a decoder, check markSupported() before relying on reset
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
- BufferedInputStream is closed
- Stream is closed
- Mark has been invalidated, pos: {pos} markLimit: {marklimit}
- File too large to map into memory
- File unsuitable for memory mapping
AI-assisted analysis of bumptech/glide@eb14a895d8 (2026-08-14).
Data as JSON: /api/errors/22f170cbe2ad4023.
Report an issue: GitHub.