juicedata/juicefs · error
failed to get the last byte: expect "o", but got %q, error:
Error message
failed to get the last byte: expect "o", but got %q, error: %s
What it means
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.
Source
Thrown at cmd/objbench.go:821
return fmt.Errorf("get not existed object should failed: %s", err)
}
return nil
})
runCase("get partial object", func(blob object.ObjectStorage) error {
br := []byte("hello")
if err := blob.Put(ctx, key, bytes.NewReader(br)); err != nil {
return fmt.Errorf("put object failed: %s", err)
}
defer blob.Delete(ctx, key) //nolint:errcheck
// get first
if d, e := get(blob, key, 0, 1); e != nil || d != "h" {
return fmt.Errorf(`failed to get the first byte:, expect "h", but got %q, error: %s`, d, e)
}
// get last
if d, e := get(blob, key, 4, 1); e != nil || d != "o" {
return fmt.Errorf(`failed to get the last byte: expect "o", but got %q, error: %s`, d, e)
}
// get last 3
if d, e := get(blob, key, 2, 3); e != nil || d != "llo" {
return fmt.Errorf(`failed to get the last three bytes: expect "llo", but got %q, error: %s`, d, e)
}
// get middle
if d, e := get(blob, key, 2, 2); e != nil || d != "ll" {
return fmt.Errorf(`failed to get two bytes: expect "ll", but got %q, error: %s`, d, e)
}
// get the end out of range
if d, e := get(blob, key, 4, 2); e != nil || d != "o" {
return warning(fmt.Errorf(`failed to get object with the end out of range, expect "o", but got %q, error: %s`, d, e))
}
// get the off out of range
if d, e := get(blob, key, 6, 2); e != nil || d != "" {
return warning(fmt.Errorf(`failed to get object with the offset out of range, expect "", but got %q, error: %s`, d, e))
}
return nilView on GitHub (pinned to c9a67b23e8)
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).
Example fix
// before (off-by-one in custom storage) end := off + limit // bytes=4-5 -> 416 or wrong data // after end := off + limit - 1 // inclusive end, bytes=4-4 -> "o"
Defensive patterns
Strategy: try-catch
Validate before calling
// verify tail range directly:
rc, err := blob.Get(ctx, key, size-1, 1)
if err != nil { return fmt.Errorf("tail get failed: %w", err) } Try / catch
d, e := get(blob, key, 4, 1)
if e != nil {
var httpErr interface{ StatusCode() int }
if errors.As(e, &httpErr) { log.Printf("HTTP status: %d", httpErr.StatusCode()) }
log.Printf("tail byte read failed: %v", e)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to get the first byte:, expect "h", but got %q, error
- failed to get the last three bytes: expect "llo", but got %q
- failed to get two bytes: expect "ll", but got %q, error: %s
- failed to get object with the end out of range, expect "o",
- ErrNotSUP
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/004f7e7d6a02ad1b.
Report an issue: GitHub.