lionsoul2014/ip2region · error

handle read: %w

Error message

handle read: %w

What it means

Go xdb searcher file-mode read: the Read after a successful Seek failed (or the handle was invalidated); the wrapped error carries the OS-level read failure — usually the xdb file was removed, replaced, or the disk errored mid-search.

Source

Thrown at binding/golang/xdb/searcher.go:222

// do the data read operation based on the setting.
// content buffer first or will read from the file.
// this operation will invoke the Seek for file based read.
func (s *Searcher) read(offset int64, buff []byte) error {
	if s.contentBuff != nil {
		cLen := copy(buff, s.contentBuff[offset:])
		if cLen != len(buff) {
			return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
		}
	} else {
		_, err := s.dbReader.Seek(offset, 0)
		if err != nil {
			return fmt.Errorf("seek to %d: %w", offset, err)
		}

		s.ioCount++
		rLen, err := s.dbReader.Read(buff)
		if err != nil {
			return fmt.Errorf("handle read: %w", err)
		}

		if rLen != len(buff) {
			return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
		}
	}

	return nil
}

View on GitHub (pinned to c1a1fc7d59)

Solutions

  1. Verify the xdb file size matches header.Length and re-download if truncated.
  2. Reopen the searcher after the file changes.
  3. Retry once on transient I/O errors; xdb reads are stateless (each read seeks first).
  4. Inspect the wrapped cause for io.EOF vs fs-level errors.

Example fix

// before
region, err := searcher.Search(ctx, ip)
// after
region, err := searcher.Search(ctx, ip)
if err != nil {
    if errors.Is(err, io.EOF) || errors.Is(err, syscall.EIO) {
        searcher, err = reloadSearcher() // reopen + retry
    }
}
Defensive patterns

Strategy: retry

Validate before calling

fi, _ := os.Stat(xdbPath)
hdr, _ := xdb.LoadHeaderFromFile(xdbPath)
if fi.Size() < int64(hdr.Length) {
    return fmt.Errorf("xdb truncated on disk")
}

Try / catch

var region string
var err error
for i := 0; i < 2; i++ {
    region, err = searcher.Search(ctx, ip)
    if err == nil { break }
    if !strings.Contains(err.Error(), "handle read") { break }
    searcher, _ = reopenSearcher() // transient I/O: reopen once and retry
}

Prevention

When it happens

Trigger: Disk/network I/O error mid-read; reading at an offset at/after EOF because the xdb is truncated or the offset is corrupted; reading from a special/pipe-backed file that errors on read.

Common situations: Truncated xdb on disk; flaky NFS/S3-mounted file; file replaced by a shorter version while a searcher holds the old descriptor.

Related errors


AI-assisted analysis of lionsoul2014/ip2region@c1a1fc7d59 (2026-09-02). Data as JSON: /api/errors/df56b21576c856f7. Report an issue: GitHub.