juicedata/juicefs · error

failed to get the last three bytes: expect "llo", but got %q

Error message

failed to get the last three bytes: expect "llo", but got %q, error: %s

What it means

Emitted by the 'get partial object' case of `juicefs objbench` when a middle-to-end ranged read (offset 2, size 3) of the 5-byte "hello" object does not return "llo". This validates multi-byte range reads that end exactly at the object's last byte. A failure means the backend's offset+limit slicing or Range request translation is wrong, or the read itself errored.

Source

Thrown at cmd/objbench.go:825

	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 nil
	})

	runCase("head an object", func(blob object.ObjectStorage) error {
		br := []byte("hello")

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Read the appended error (%s) for the underlying storage failure.
  2. Verify with a direct ranged request (`--range bytes=2-4`) against the backend.
  3. Fix end-offset computation in the ObjectStorage implementation (inclusive vs exclusive end).
  4. Re-run objbench to confirm all partial-get cases pass together.

Example fix

// before
buf := data[off : off+limit+1] // returns "llo\0" or panics
// after
buf := data[off : off+limit] // returns "llo"
Defensive patterns

Strategy: validation

Validate before calling

// check the backend returns exactly the requested slice:
rc, err := blob.Get(ctx, key, 2, 3)
if err != nil { return err }
buf, _ := io.ReadAll(rc); rc.Close()
if string(buf) != "llo" { return fmt.Errorf("range 2+3 got %q", buf) }

Type guard

func isExactRange(d string, want string, err error) bool { return err == nil && d == want }

Try / catch

d, e := get(blob, key, 2, 3)
if e != nil || d != "llo" {
    log.Printf("range read mismatch: got=%q err=%v", d, e)
    // fallback: full read + local slice
}

Prevention

When it happens

Trigger: ObjectStorage.Get(key, off=2, size=3) returns data other than "llo", returns the full object, returns fewer bytes, or returns an error (HTTP 416, truncation, network failure).

Common situations: Custom storage layers that pass an exclusive end as inclusive (or vice versa) to the underlying API; S3 gateways mangling `bytes=2-4`; backends that clamp size incorrectly near EOF; cached/stale objects of different length.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/fd36c8f401d05450. Report an issue: GitHub.