#5788·go-zero

Bug: filex.SplitLineChunks returns EOF error when the last line is longer than the preferred chunk size

Author: purplecityCreated Sep 25, 2026Updated Sep 27, 2026

Bug: filex.SplitLineChunks returns EOF error when the last line is longer than the preferred chunk size

Description

filex.SplitLineChunks returns an unexpected EOF error for a perfectly valid file when the last line is longer than preferSize (i.e. the ideal cut point falls inside the final line).

Related: found while investigating #5787 (another edge case in core/filex).

Reproduction

go
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/zeromicro/go-zero/core/filex"
)

func main() {
	// last line (3000 bytes) is longer than preferSize
	content := "a\n" + strings.Repeat("b", 3000)

	dir, _ := os.MkdirTemp("", "repro")
	defer os.RemoveAll(dir)
	f := filepath.Join(dir, "t.txt")
	os.WriteFile(f, []byte(content), 0o644)

	ranges, err := filex.SplitLineChunks(f, 2)
	fmt.Printf("ranges=%v, err=%v\n", ranges, err)
}

Output:

ranges=[], err=EOF

Expected: no error, and since the last line must not be split across chunks (documented guarantee "The whole line are guaranteed to be split in the same chunk"), one chunk [0, 3002) covering the whole file.

Root cause

SplitLineChunks estimates each chunk's ideal stop as offset + preferSize and then calls nextRange → skipPartialLine to push the stop forward to the start of the next line.

skipPartialLine implicitly assumes there is always a next line after the cut point. When the cut point falls inside the last line, there is no next line: it scans forward to EOF without finding a '\n', and returns io.EOF:

go
if n == 0 {
    return 0, io.EOF
}

nextRange and SplitLineChunks then propagate this as an error:

go
offsetRange, err := nextRange(file, offset, offset+preferSize)
if err != nil {
    return nil, err   // io.EOF leaks to the caller
}

Trigger condition: offset + preferSize < fileSize (so the top-of-loop tail check doesn't fire) and there is no '\n' in [offset+preferSize, fileSize) — i.e. the ideal cut point lands inside the final line. Note the tail check offset+preferSize >= info.Size() only protects the final short tail; it cannot protect a long last line.

Suggested fix

Special-case io.EOF in SplitLineChunks: it precisely means "the cut point is inside the last line", so merge the last line entirely into the current chunk with Stop = info.Size():

go
offsetRange, err := nextRange(file, offset, offset+preferSize)
if errors.Is(err, io.EOF) {
    // cut point is inside the last line; keep the line whole
    offsetRange = OffsetRange{
        File:  filename,
        Start: offset,
        Stop:  info.Size(),
    }
} else if err != nil {
    return nil, err
}

The existing if offsetRange.Stop < info.Size() check then exits the loop naturally (no empty trailing chunk).

Verified with: normal multi-line files (unchanged output), cut point landing on trailing newlines (unchanged), cut point inside last line at offset 0 and mid-file (previously EOF error, now a single whole-file/whole-line chunk).

Environment

  • go-zero: master (core/filex/lookup.go)
  • go version go1.27.1 darwin/arm64