valyala/fasthttp · error · ErrBodyStreamWritePanic

panic while writing body stream: %+v

Error message

panic while writing body stream: %+v

What it means

When a response body is a stream (BodyStream set), fasthttp writes it inside a recover guard; if the stream's Write panics, the panic is converted into an ErrBodyStreamWritePanic wrapping this message plus the recovered value. This prevents a user-supplied stream panic from crashing the server/client goroutine.

Source

Thrown at http.go:2350

		}
	}
	errc := req.closeBodyStream()
	if err == nil {
		err = errc
	}
	return err
}

// ErrBodyStreamWritePanic is returned when panic happens during writing body stream.
type ErrBodyStreamWritePanic struct {
	error
}

func (resp *Response) writeBodyStream(w *bufio.Writer, sendBody bool) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = &ErrBodyStreamWritePanic{
				error: fmt.Errorf("panic while writing body stream: %+v", r),
			}
		}
	}()

	contentLength := resp.Header.ContentLength()
	if contentLength < 0 {
		lrSize := limitedReaderSize(resp.bodyStream)
		if lrSize >= 0 {
			contentLength = int(lrSize)
			if int64(contentLength) != lrSize {
				contentLength = -1
			}
			if contentLength >= 0 {
				resp.Header.SetContentLength(contentLength)
			}
		}
	}
	if contentLength >= 0 {

View on GitHub (pinned to c96f600972)

Solutions

  1. Fix the panicking code in your BodyStream reader/writer (inspect the %+v value for the panic message and stack hint).
  2. Recover inside your own stream implementation and return an error from Read instead of panicking.
  3. Verify the size hint passed to SetBodyStream matches reality to avoid mismatched writes.
  4. Test streams with concurrent Close/Read to surface races (go test -race).

Example fix

// before
resp.SetBodyStream(riskyReader, -1) // riskyReader panics on EOF
// after
resp.SetBodyStream(safeReader{r: riskyReader}, -1)

type safeReader struct{ r io.Reader }
func (s safeReader) Read(p []byte) (int, error) {
    defer func() { if r := recover(); r != nil { /* log */ } }()
    return s.r.Read(p)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if r == nil { return errors.New("nil body stream reader") }

Type guard

func nonPanicReader(r io.Reader) io.Reader {
    return panicSafeReader{r}
}
type panicSafeReader struct{ io.Reader }
func (p panicSafeReader) Read(b []byte) (n int, err error) {
    defer func() {
        if rec := recover(); rec != nil {
            err = fmt.Errorf("stream read panic: %v", rec)
        }
    }()
    return p.Reader.Read(b)
}

Try / catch

// fasthttp already converts panics; handle the typed error
if err := ctx.Write(respBody); err != nil {
    var pe *fasthttp.ErrBodyStreamWritePanic
    if errors.As(err, &pe) {
        log.Printf("body stream panicked: %v", pe.Unwrap())
    }
    return err
}

Prevention

When it happens

Trigger: Setting resp.SetBodyStream(reader, size) (server or client) where the reader's Read or the wrapper Write panics — nil map access, index out of range, or a nil reader dereference inside custom streaming code.

Common situations: Streaming from a generator function that panics on exhaustion; passing a reader that panics when read after Close; data races on shared state accessed inside the stream's Read.

Related errors


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