{"record":{"id":"a8eee32f6eea22b9","repo":"apache/hadoop","slug":"expected-bytes-but-read","errorCode":null,"errorMessage":"Expected {} bytes, but read {}","messagePattern":"Expected (.+?) bytes, but read (.+?)","errorType":"validation","errorClass":"InvalidRecordException","httpStatus":null,"severity":"error","filePath":"hadoop-common-project/hadoop-registry/src/main/java/org/apache/hadoop/registry/client/impl/FSRegistryOperationsService.java","lineNumber":171,"sourceCode":"      stream.close();\n      LOG.info(\"Bound record to path \" + dataPath);\n    }\n  }\n\n  @Override\n  public ServiceRecord resolve(String path) throws PathNotFoundException,\n      NoRecordException, InvalidRecordException, IOException {\n    // Read the entire file into byte array, should be small metadata\n\n    Long size = fs.getFileStatus(formatDataPath(path)).getLen();\n    byte[] bytes = new byte[size.intValue()];\n\n    FSDataInputStream instream = fs.open(formatDataPath(path));\n    int bytesRead = instream.read(bytes);\n    instream.close();\n\n    if (bytesRead < size) {\n      throw new InvalidRecordException(path,\n          \"Expected \" + size + \" bytes, but read \" + bytesRead);\n    }\n\n    // Unmarshal, check, and return\n    ServiceRecord record = serviceRecordMarshal.fromBytes(path, bytes);\n    RegistryTypeUtils.validateServiceRecord(path, record);\n    return record;\n  }\n\n  @Override\n  public RegistryPathStatus stat(String path)\n      throws PathNotFoundException, InvalidPathnameException, IOException {\n    FileStatus fstat = fs.getFileStatus(formatDataPath(path));\n    int numChildren = fs.listStatus(makePath(path)).length;\n\n    RegistryPathStatus regstat =\n        new RegistryPathStatus(fstat.getPath().toString(),\n            fstat.getModificationTime(), fstat.getLen(), numChildren);","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-registry/src/main/java/org/apache/hadoop/registry/client/impl/FSRegistryOperationsService.java#L153-L189","documentation":"FSRegistryOperationsService.resolve() reads a record file by first sizing it with getFileStatus().getLen(), allocating a byte array of that length, and then issuing a single instream.read(bytes) call. InputStream.read(byte[]) only guarantees at least one byte, not a full buffer, so if the single call returns fewer bytes than the earlier stat reported, resolve() throws InvalidRecordException('Expected N bytes, but read M'). In practice the mismatch almost always means the record file was rewritten or truncated between the getFileStatus call and the read.","triggerScenarios":"A concurrent bind()/delete() of the same registry path between the getFileStatus and the fs.open/read sequence; the record file being truncated or replaced mid-resolve; a filesystem input stream that legitimately returns short reads on one call.","commonSituations":"Multiple writers publishing/unpublishing the same service record at the same time; a resolve racing an unregister; the FS registry backend used under concurrent service discovery traffic. The single-read() pattern is itself a latent defect — readFully would be correct.","solutions":["Retry resolve() a small bounded number of times with a short delay — the window between stat and read is tiny and the next attempt usually sees a consistent file.","Eliminate concurrent writers to the same registry path (single publisher per path, complete bind before advertising the path).","If you control the storage layer, patch resolve() to loop until EOF (readFully semantics) instead of a single read() call."],"exampleFix":"// before (FSRegistryOperationsService.resolve)\nint bytesRead = instream.read(bytes);\ninstream.close();\nif (bytesRead < size) {\n  throw new InvalidRecordException(path, \"Expected \" + size + \" bytes, but read \" + bytesRead);\n}\n\n// after: read fully; only fail on a genuine size mismatch\nlong total = 0;\nint n;\nwhile (total < bytes.length\n    && (n = instream.read(bytes, (int) total, bytes.length - (int) total)) != -1) {\n  total += n;\n}\ninstream.close();\nif (total != size) {\n  throw new InvalidRecordException(path, \"Expected \" + size + \" bytes, but read \" + total);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"ServiceRecord resolveWithRetry(RegistryOperations ops, String path, int attempts)\n    throws IOException {\n  IOException last = null;\n  for (int i = 0; i < attempts; i++) {\n    try {\n      return ops.resolve(path);\n    } catch (InvalidRecordException e) {\n      last = e; // record file changed size between stat and read: transient\n    }\n  }\n  throw last;\n}","preventionTips":["Keep exactly one writer per registry path; complete bind() or delete() before advertising the path to consumers.","Do not treat a single resolve failure as permanent — re-resolve once after a short delay before giving up.","In your own FS code never rely on a single InputStream.read(byte[]) to fill the buffer; use readFully or a loop."],"tags":["registry","io","concurrency","short-read","filesystem"],"backgroundTag":"partial-read","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}