{"record":{"id":"004f7e7d6a02ad1b","repo":"juicedata/juicefs","slug":"failed-to-get-the-last-byte-expect-o-but-got","errorCode":null,"errorMessage":"failed to get the last byte: expect \"o\", but got %q, error: %s","messagePattern":"failed to get the last byte: expect \"o\", but got %q, error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/objbench.go","lineNumber":821,"sourceCode":"\t\t\treturn fmt.Errorf(\"get not existed object should failed: %s\", err)\n\t\t}\n\t\treturn nil\n\t})\n\n\trunCase(\"get partial object\", func(blob object.ObjectStorage) error {\n\t\tbr := []byte(\"hello\")\n\t\tif err := blob.Put(ctx, key, bytes.NewReader(br)); err != nil {\n\t\t\treturn fmt.Errorf(\"put object failed: %s\", err)\n\t\t}\n\t\tdefer blob.Delete(ctx, key) //nolint:errcheck\n\n\t\t// get first\n\t\tif d, e := get(blob, key, 0, 1); e != nil || d != \"h\" {\n\t\t\treturn fmt.Errorf(`failed to get the first byte:, expect \"h\", but got %q, error: %s`, d, e)\n\t\t}\n\t\t// get last\n\t\tif d, e := get(blob, key, 4, 1); e != nil || d != \"o\" {\n\t\t\treturn fmt.Errorf(`failed to get the last byte: expect \"o\", but got %q, error: %s`, d, e)\n\t\t}\n\t\t// get last 3\n\t\tif d, e := get(blob, key, 2, 3); e != nil || d != \"llo\" {\n\t\t\treturn fmt.Errorf(`failed to get the last three bytes: expect \"llo\", but got %q, error: %s`, d, e)\n\t\t}\n\t\t// get middle\n\t\tif d, e := get(blob, key, 2, 2); e != nil || d != \"ll\" {\n\t\t\treturn fmt.Errorf(`failed to get two bytes: expect \"ll\", but got %q, error: %s`, d, e)\n\t\t}\n\t\t// get the end out of range\n\t\tif d, e := get(blob, key, 4, 2); e != nil || d != \"o\" {\n\t\t\treturn warning(fmt.Errorf(`failed to get object with the end out of range, expect \"o\", but got %q, error: %s`, d, e))\n\t\t}\n\t\t// get the off out of range\n\t\tif d, e := get(blob, key, 6, 2); e != nil || d != \"\" {\n\t\t\treturn warning(fmt.Errorf(`failed to get object with the offset out of range, expect \"\", but got %q, error: %s`, d, e))\n\t\t}\n\t\treturn nil","sourceCodeStart":803,"sourceCodeEnd":839,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/cmd/objbench.go#L803-L839","documentation":"Same 'get partial object' case in `juicefs objbench`; this fires when the ranged GET of the last byte (offset 4, size 1) of the \"hello\" object does not return \"o\". It checks that the backend can seek to the end of an object and read a tail range correctly. Failures indicate off-by-one handling of ranges at the object end or a storage error.","triggerScenarios":"ObjectStorage.Get(key, off=4, size=1) against a 5-byte object returns wrong bytes, empty data, or an error — e.g. backends that treat `Range: bytes=4-4` incorrectly or reject end-of-object ranges.","commonSituations":"S3-compatible endpoints that mis-handle Range headers near EOF; MinIO/proxy misconfiguration; custom storage implementations with off-by-one limit arithmetic; eventually-consistent stores returning the pre-write object.","solutions":["Check the embedded error (%s) for the root cause (HTTP status, auth, timeout).","Test tail ranges directly with `aws s3api get-object --range bytes=4-4` to isolate the backend.","Fix range/limit arithmetic in the custom ObjectStorage implementation.","Confirm object size with Head and ensure the test's Put completed before the Get (eventual consistency)."],"exampleFix":"// before (off-by-one in custom storage)\nend := off + limit // bytes=4-5 -> 416 or wrong data\n// after\nend := off + limit - 1 // inclusive end, bytes=4-4 -> \"o\"","handlingStrategy":"try-catch","validationCode":"// verify tail range directly:\nrc, err := blob.Get(ctx, key, size-1, 1)\nif err != nil { return fmt.Errorf(\"tail get failed: %w\", err) }","typeGuard":null,"tryCatchPattern":"d, e := get(blob, key, 4, 1)\nif e != nil {\n    var httpErr interface{ StatusCode() int }\n    if errors.As(e, &httpErr) { log.Printf(\"HTTP status: %d\", httpErr.StatusCode()) }\n    log.Printf(\"tail byte read failed: %v\", e)\n}","preventionTips":["Test end-of-object ranges (last byte) explicitly in storage integration tests.","Beware inclusive vs exclusive end offsets when translating to HTTP Range headers.","Confirm object length via Head before asserting tail content."],"tags":["object-storage","range-read","benchmark","off-by-one"],"backgroundTag":"unexpected-response-shape","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}