valyala/fasthttp · error

bufio read returned (%d, nil)

Error message

bufio read returned (%d, nil)

What it means

This error is returned by readBodyIdentity in fasthttp when the underlying bufio.Reader.Read call violates the io.Reader contract by returning 0 bytes read with a nil error. The io.Reader contract requires that a Read returning n <= 0 must also return a non-nil error, so this guards against a misbehaving underlying reader (custom conns, wrapped transports). It is a defensive invariant check, not a protocol error.

Source

Thrown at http.go:2879

	return b, nil
}

func readBodyIdentity(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) {
	dst = dst[:cap(dst)]
	if len(dst) == 0 {
		dst = make([]byte, 1024)
	}
	offset := 0
	for {
		nn, err := r.Read(dst[offset:])
		if nn <= 0 {
			switch {
			case errors.Is(err, io.EOF):
				return dst[:offset], nil
			case err != nil:
				return dst[:offset], err
			default:
				return dst[:offset], fmt.Errorf("bufio read returned (%d, nil)", nn)
			}
		}
		offset += nn
		if maxBodySize > 0 && offset > maxBodySize {
			return dst[:offset], ErrBodyTooLarge
		}
		if len(dst) == offset {
			n := roundUpForSliceCap(2 * offset)
			if maxBodySize > 0 && n > maxBodySize {
				n = maxBodySize + 1
			}
			b := make([]byte, n)
			copy(b, dst)
			dst = b
		}
	}
}

View on GitHub (pinned to c96f600972)

Solutions

  1. Fix the custom io.Reader/net.Conn wrapper so it never returns n==0 with err==nil; return io.EOF at end of stream instead.
  2. Check recently added middleware or conn wrappers and replace them with a contract-conforming implementation.
  3. If it reproduces with stock fasthttp and no wrappers, report it to valyala/fasthttp with a minimal repro.
  4. As a workaround, use a plain connection without the custom wrapper to confirm it is the source.

Example fix

// before (bad custom reader)
func (r *myConn) Read(p []byte) (int, error) {
    if r.pending == 0 { return 0, nil } // violates io.Reader contract
    ...
}
// after
func (r *myConn) Read(p []byte) (int, error) {
    if r.pending == 0 { return 0, io.EOF }
    ...
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Audit any custom io.Reader/net.Conn wrappers before enabling:
// they must never return (0, nil).
func checkReader(r io.Reader) error {
    buf := make([]byte, 1)
    n, err := r.Read(buf)
    if n == 0 && err == nil {
        return errors.New("reader violates io.Reader contract: (0, nil)")
    }
    return nil
}

Type guard

if err != nil && strings.Contains(err.Error(), "bufio read returned") {
    // invariant violation: reader returned (0, nil); not an ErrBrokenChunk
}

Try / catch

b, err := ... // body read
if err != nil {
    if strings.Contains(err.Error(), "bufio read returned") {
        log.Printf("underlying reader violates io.Reader contract: %v", err)
        // replace/inspect custom conn wrapper
    }
    return err
}

Prevention

When it happens

Trigger: Reading an identity-encoded (non-chunked) request/response body via Server/Client when the conn's bufio.Reader.Read returns (0, nil) — typically caused by a custom net.Conn or a wrapper implementing io.Reader incorrectly.

Common situations: Custom transport/wrapper code (compression, logging, TLS-shim conns) around the connection returning zero bytes without an error; testing with mocked conns that don't follow the io.Reader contract.

Related errors


AI-assisted analysis of valyala/fasthttp@c96f600972 (2026-08-31). Data as JSON: /api/errors/2d485a274710fc90. Report an issue: GitHub.