{"record":{"id":"22f170cbe2ad4023","repo":"bumptech/glide","slug":"cannot-reset-to-unset-mark-position","errorCode":null,"errorMessage":"Cannot reset to unset mark position","messagePattern":"Cannot reset to unset mark position","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"library/src/main/java/com/bumptech/glide/util/ByteBufferUtil.java","lineNumber":310,"sourceCode":"    @Override\n    public boolean markSupported() {\n      return true;\n    }\n\n    @Override\n    public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) {\n      if (!byteBuffer.hasRemaining()) {\n        return -1;\n      }\n      int toRead = Math.min(byteCount, available());\n      byteBuffer.get(buffer, byteOffset, toRead);\n      return toRead;\n    }\n\n    @Override\n    public synchronized void reset() throws IOException {\n      if (markPos == UNSET) {\n        throw new IOException(\"Cannot reset to unset mark position\");\n      }\n      // reset() was not implemented correctly in 4.0.4, so we track the mark position ourselves.\n      byteBuffer.position(markPos);\n    }\n\n    @Override\n    public long skip(long byteCount) {\n      if (!byteBuffer.hasRemaining()) {\n        return -1;\n      }\n\n      long toSkip = Math.min(byteCount, available());\n      byteBuffer.position((int) (byteBuffer.position() + toSkip));\n      return toSkip;\n    }\n  }\n}\n","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/bumptech/glide/blob/eb14a895d8f866e6a08c3e19d50ea3bd2c12c20a/library/src/main/java/com/bumptech/glide/util/ByteBufferUtil.java#L292-L328","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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"],"exampleFix":"// before\nval src = ByteBufferUtil.toStream(buf)\nsrc.reset() // IOException: no mark set\n\n// after\nval src = ByteBufferUtil.toStream(buf)\nsrc.mark(0)\n// ... read ...\nsrc.reset()","handlingStrategy":"validation","validationCode":"val src = ByteBufferUtil.toStream(buf)\nif (src.markSupported()) src.mark(0)\n// ... read ...\nif (src.markSupported()) src.reset()","typeGuard":null,"tryCatchPattern":"try {\n  stream.reset()\n} catch (e: IOException) {\n  // no mark set — re-derive a fresh stream from the buffer\n  freshStream = ByteBufferUtil.toStream(buf.duplicate())\n}","preventionTips":["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"],"tags":["android","glide","io","stream","decoder"],"backgroundTag":null,"analyzedSha":"eb14a895d8f866e6a08c3e19d50ea3bd2c12c20a","analyzedAt":"2026-08-14T01:43:54.821Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}