jackc/pgx · error
invalid read of large object
Error message
invalid read of large object
What it means
Returned by LargeObject.Read when PostgreSQL's loread() built-in returns MORE bytes than the requested length. The read loop computes an 'expected' byte count that always fits into the caller's buffer, so the server returning extra bytes is a protocol violation. Per the code comment, PreallocBytes.Scan should never need to allocate a new slice either. This is essentially a sanity guard against a misbehaving server or corrupted large-object stream.
Source
Thrown at large_objects.go:132
if expected == 0 {
break
} else if expected > maxLargeObjectMessageLength {
expected = maxLargeObjectMessageLength
}
res := pgtype.PreallocBytes(p[nTotal:])
err := o.tx.QueryRow(o.ctx, "select loread($1, $2)", o.fd, expected).Scan(&res)
// We compute expected so that it always fits into p, so it should never happen
// that PreallocBytes's ScanBytes had to allocate a new slice.
nTotal += len(res)
if err != nil {
return nTotal, err
}
if len(res) < expected {
return nTotal, io.EOF
} else if len(res) > expected {
return nTotal, errors.New("invalid read of large object")
}
}
return nTotal, nil
}
// Seek moves the current location pointer to the new location specified by offset.
func (o *LargeObject) Seek(offset int64, whence int) (n int64, err error) {
err = o.tx.QueryRow(o.ctx, "select lo_lseek64($1, $2, $3)", o.fd, offset, whence).Scan(&n)
return n, err
}
// Tell returns the current read or write location of the large object descriptor.
func (o *LargeObject) Tell() (n int64, err error) {
err = o.tx.QueryRow(o.ctx, "select lo_tell64($1)", o.fd).Scan(&n)
return n, err
}
View on GitHub (pinned to ec1a0befd2)
Solutions
- Verify you are reading a valid, open large-object descriptor obtained from lo_open within the same transaction.
- Ensure no concurrent Truncate/Write is mutating the large object during the Read.
- Test against a stock PostgreSQL to rule out server/proxy misbehavior; if a proxy/middleware is in front, check its loread forwarding logic.
- If using a non-vanilla server, report the protocol violation upstream.
Example fix
// before: read into a buffer while another goroutine truncates
lo.Read(buf)
// after: perform reads and truncation serially in one transaction
n, err := lo.Read(buf)
if err != nil {
return fmt.Errorf("lo read failed: %w", err)
} Defensive patterns
Strategy: try-catch
Try / catch
// LargeObject.Read already returns the error via io.Reader interface.
n, err := lo.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
// normal end of large object
return n, nil
}
return n, fmt.Errorf("large object read failed (possible server protocol violation): %w", err)
} Prevention
- Keep reads, writes, and truncation on a given large object within a single serial transaction.
- Validate the fd came from lo_open in the same Tx before reading.
- Against non-vanilla PostgreSQL-compatible servers, regression-test large-object round trips.
When it happens
Trigger: Calling (*LargeObject).Read(p []byte) (large_objects.go:110) where the underlying 'select loread($1, $2)' query returns a byte slice longer than the 'expected' argument passed. Only reachable when the server violates the loread contract.
Common situations: Connecting to a buggy/proxied PostgreSQL-compatible server (e.g. a sharded middleware or a fork) that mishandles loread length; a corrupted pg_largeobject table; extremely rarely against vanilla PostgreSQL. Also seen with concurrent Truncate/Write racing a Read.
Related errors
- failed to remove large object
- failed to write to large object
- short write to large object
- invalid write to large object
- server does not support SCRAM-SHA-256
AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04).
Data as JSON: /data/errors/9921cdd483d94cee.json.
Report an issue: GitHub.