Bug: filex.SplitLineChunks returns EOF error when the last line is longer than the preferred chunk size
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
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=EOFExpected: 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:
if n == 0 {
return 0, io.EOF
}nextRange and SplitLineChunks then propagate this as an error:
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():
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
Source: zeromicro/go-zero