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

  1. Verify you are reading a valid, open large-object descriptor obtained from lo_open within the same transaction.
  2. Ensure no concurrent Truncate/Write is mutating the large object during the Read.
  3. Test against a stock PostgreSQL to rule out server/proxy misbehavior; if a proxy/middleware is in front, check its loread forwarding logic.
  4. 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

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


AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04). Data as JSON: /data/errors/9921cdd483d94cee.json. Report an issue: GitHub.