gofr-dev/gofr · error

stream source panicked

Error message

stream source panicked

What it means

errStreamPanic is declared in pkg/gofr/http/stream.go and signals that the Streamer source backing an HTTP stream panicked during execution. The streaming machinery recovers the panic (see readStream) and converts it into this error so the client connection is terminated cleanly instead of crashing the process.

Source

Thrown at pkg/gofr/http/stream.go:19

package http

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"

	resTypes "gofr.dev/pkg/gofr/http/response"
)

const (
	defaultHeartbeat  = 15 * time.Second
	streamWriteWindow = 30 * time.Second
)

var (
	errStreamPanic   = errors.New("stream source panicked")
	errNilStream     = errors.New("stream source is nil")
	errStreamCorrupt = errors.New("stream value could not be encoded")
)

// handleStream drains s.Source to the client, flushing after every write. It pulls values on
// demand so a slow client throttles the producer, sends a periodic keep-alive so a dropped client
// is detected while idle, bounds each write with a deadline, and always closes the source — leaving
// no goroutine behind when the client disconnects mid-stream. A Source that also honors its own
// context cancels promptly even without a heartbeat.
func (r Responder) handleStream(s resTypes.Stream) {
	if s.Source == nil {
		r.w.WriteHeader(http.StatusInternalServerError)
		_, _ = r.w.Write([]byte(`{"error":{"message":"` + errNilStream.Error() + `"}}` + "\n"))

		return
	}

	rc := http.NewResponseController(r.w)

View on GitHub (pinned to 187eb24962)

Solutions

  1. Find the panic value in the wrapped error message ('stream source panicked: <p>') to locate the faulting code
  2. Fix the underlying panic in your Streamer implementation
  3. Add defensive checks (nil guards, bounds checks) inside the source before it produces values
  4. Recover and convert expected failure modes into ordinary errors inside your source instead of panicking

Example fix

// before
func (s *mySource) Stream(items chan<- any) {
    for _, v := range s.data { // panics if s.data is nil
        items <- v
    }
}
// after
func (s *mySource) Stream(items chan<- any) {
    if s.data == nil {
        return
    }
    for _, v := range s.data {
        items <- v
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if source == nil {
    return errors.New("stream source must not be nil")
}
if err := source.Validate(); err != nil {
    return err
}

Type guard

func hasValidSource(s *Stream) bool {
    return s != nil && s.Source != nil && !reflect.ValueOf(s.Source).IsNil()
}

Try / catch

err := runStream(ctx, src)
if err != nil {
    if errors.Is(err, errStreamPanic) {
        log.Printf("stream source panicked: %v", err)
        reconnectOrFallback(ctx)
        return
    }
    return err
}

Prevention

When it happens

Trigger: A user-supplied resTypes.Streamer source panics (nil map write, index out of range, explicit panic) while readStream is pulling values; the recovered panic is wrapped as '%w: %v' with errStreamPanic and sent over the terminal error channel.

Common situations: Buggy custom stream generators (nil pointer dereference on business objects); sources that panic on channel close; data-dependent panics mid-stream such as division by zero or type assertions on unexpected values.

Related errors


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/c67ef056441d2d46. Report an issue: GitHub.