{"record":{"id":"b674758351b6b080","repo":"apache/hadoop","slug":"multipart-upload-incomplete-expected-parts-but","errorCode":null,"errorMessage":"Multipart upload incomplete: expected {} parts but got {}","messagePattern":"Multipart upload incomplete: expected (.+?) parts but got (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"hadoop-cloud-storage-project/hadoop-bos/src/main/java/org/apache/hadoop/fs/bos/BosOutputStream.java","lineNumber":417,"sourceCode":"            e);\n      }\n\n      if (uploadException != null) {\n        abortMultipartUpload();\n        throw new IOException(\n            \"Multipart upload failed\", uploadException);\n      }\n\n      LOG.debug(\n          \"success to wait upload part threads done\");\n\n      LOG.debug(\n          \"Size of eTags is {}. blkIndex is {}\",\n          this.eTags.size(), this.blkIndex);\n\n      if (this.eTags.size() != this.blkIndex - 1) {\n        abortMultipartUpload();\n        throw new IOException(\n            \"Multipart upload incomplete: expected \"\n                + (this.blkIndex - 1) + \" parts but got \"\n                + this.eTags.size());\n      }\n\n      completeMultipartUpload(\n          bucketName, this.key,\n          this.uploadId, this.eTags);\n    }\n\n    super.close();\n    this.closed = true;\n  }\n\n  /**\n   * Aborts the in-progress multipart upload to prevent\n   * storage leakage on failure.\n   */","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-cloud-storage-project/hadoop-bos/src/main/java/org/apache/hadoop/fs/bos/BosOutputStream.java#L399-L435","documentation":"After close() waits for all part threads, it compares the collected eTags count against blkIndex - 1 (blocks uploaded). A mismatch means some thread finished without recording an ETag — a silent or lost failure with no uploadException — so completeMultipartUpload cannot be issued. The upload is aborted server-side and IOException 'Multipart upload incomplete: expected N parts but got M' is thrown; like error 37, no object is created.","triggerScenarios":"An upload thread ends without propagating its exception or its ETag: task cancellation racing close(), JVM/thread-pool shutdown during commit, or a race in result collection after an earlier swallowed InterruptedException in the wait loop.","commonSituations":"Forcible task kill or speculation cancellation exactly at commit time; executor shutdown hooks closing streams on an interrupted thread; usually preceded by interrupt/timeout warnings in the logs.","solutions":["Scan the preceding logs for interrupted or failed upload threads — something silently dropped a part result","Ensure close() runs on a healthy, non-interrupted thread; avoid killing tasks during their commit phase","Retry the write from scratch; the abort guarantees no partial object remains","Upgrade the connector if a known eTag-collection race is fixed in a newer hadoop-bos version"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"static boolean isMultipartIncomplete(IOException e) {\n  return e.getMessage() != null && e.getMessage().startsWith(\"Multipart upload incomplete\");\n}","tryCatchPattern":"catch (IOException e) {\n  if (isMultipartIncomplete(e)) {\n    // silent part loss: retry the whole write; ensure close() is not interrupted\n    rewriteFromSource(path);\n  } else { throw e; }\n}","preventionTips":["Never kill/interrupt tasks during their commit (close) phase","Call close() from a healthy thread; check Thread.interrupted() before commit","Keep the connector version current to pick up upload-thread race fixes"],"tags":["bos","multipart-upload","incomplete-upload","race-condition","data-loss","hadoop"],"backgroundTag":"multipart-upload-incomplete","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}