{"record":{"id":"c0f49654492529d6","repo":"apache/hadoop","slug":"cannot-seek-after-eof-c0f496","errorCode":null,"errorMessage":"Cannot seek after EOF","messagePattern":"Cannot seek after EOF","errorType":"exception","errorClass":"EOFException","httpStatus":null,"severity":"error","filePath":"hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedInputStream.java","lineNumber":357,"sourceCode":"  void updateReadStats(final StripedBlockUtil.BlockReadStats stats, long readTimeMS) {\n    if (stats == null) {\n      return;\n    }\n    updateReadStatistics(readStatistics, stats.getBytesRead(),\n        stats.isShortCircuit(), stats.getNetworkDistance());\n    dfsClient.updateFileSystemReadStats(stats.getNetworkDistance(),\n        stats.getBytesRead(), readTimeMS);\n    assert readStatistics.getBlockType() == BlockType.STRIPED;\n    dfsClient.updateFileSystemECReadStats(stats.getBytesRead());\n  }\n\n  /**\n   * Seek to a new arbitrary location.\n   */\n  @Override\n  public synchronized void seek(long targetPos) throws IOException {\n    if (targetPos > getFileLength()) {\n      throw new EOFException(\"Cannot seek after EOF\");\n    }\n    if (targetPos < 0) {\n      throw new EOFException(\"Cannot seek to negative offset\");\n    }\n    if (closed.get()) {\n      throw new IOException(\"Stream is closed!\");\n    }\n    if (targetPos <= blockEnd) {\n      final long targetOffsetInBlk = getOffsetInBlockGroup(targetPos);\n      if (curStripeRange.include(targetOffsetInBlk)) {\n        int bufOffset = getStripedBufOffset(targetOffsetInBlk);\n        curStripeBuf.position(bufOffset);\n        pos = targetPos;\n        return;\n      }\n    }\n    pos = targetPos;\n    blockEnd = -1;","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-hdfs-project/hadoop-hdfs-client/src/main/java/org/apache/hadoop/hdfs/DFSStripedInputStream.java#L339-L375","documentation":"DFSStripedInputStream.seek(long) validates the target exactly like the replicated stream: targetPos > getFileLength() throws EOFException('Cannot seek after EOF') (and negative targets / closed streams get their own errors just below). Because it compares against the live file length, a seek that was in-bounds against a stale cached length becomes 'after EOF' when the EC file has been truncated or is simply shorter than assumed.","triggerScenarios":"Calling seek(n) on a striped/erasure-coded file where n exceeds the file length - e.g. seek(cachedLen) after the file was rewritten shorter; seeking to footer offsets computed from stale metadata; seeking past EOF instead of checking remaining length first.","commonSituations":"Readers over EC-enabled warehouse paths whose files are compacted/truncated under them; index files rebuilt smaller than the cached offset table expects; porting code from replicated files where the same bug threw a different exception that happened to be caught.","solutions":["Validate against a fresh length before seeking: long len = fs.getFileStatus(path).getLen(); if (targetPos > len) handle/throw your own error.","If file lengths can change concurrently, make rewrites atomic (write temp, rename) so readers never see shrinking files, and re-stat on any EOF/seek failure.","Clamp in defensive code: in.seek(Math.min(targetPos, len)) when the tail is optional.","When seeking to a footer, first check len >= footerSize and surface a clear 'file too small' error."],"exampleFix":"// before\nin.seek(cachedFileLen - trailerSize); // EOFException when file shrank\n\n// after\nlong len = fs.getFileStatus(path).getLen();\nif (len < trailerSize) throw new IllegalStateException(path + \" too small\");\nin.seek(len - trailerSize);","handlingStrategy":"validation","validationCode":"long fileLen = fs.getFileStatus(path).getLen(); // do not trust a cached length\nif (targetPos < 0 || targetPos > fileLen) {\n  throw new IllegalArgumentException(\n      \"seek target \" + targetPos + \" outside [0, \" + fileLen + \"] for \" + path);\n}\nstripedIn.seek(targetPos);","typeGuard":null,"tryCatchPattern":"try {\n  stripedIn.seek(targetPos);\n} catch (EOFException e) {\n  long freshLen = fs.getFileStatus(path).getLen();\n  if (targetPos > freshLen) throw new IllegalStateException(\n      \"file shrank under reader: wanted \" + targetPos + \", len \" + freshLen, e);\n  throw e; // transient inconsistency - re-stat and retry once\n}","preventionTips":["Always bound seek targets by a freshly stated file length.","Re-stat on any EOFException from seek - the file may have been truncated concurrently.","Write temp-then-rename for rewrites so EC file lengths never move backwards for readers.","Check len >= footerSize before seeking to footer offsets."],"tags":["hdfs","hdfs-client","erasure-coding","striped","seek","eof"],"backgroundTag":"seek-past-eof","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}