gofiber/fiber · warning
errStreamClosed
errStreamClosed
Error message
sse: stream closed
What it means
The SSE handler tracks the stream's lifetime; once the client disconnects, the request context is canceled, or the handler returns, the stream is marked closed. Any subsequent write returns errStreamClosed to prevent writes on a dead connection from panicking or blocking.
Source
Thrown at middleware/sse/sse.go:21
// The package focuses on the SSE transport: response headers, wire formatting,
// flushing, heartbeat comments, and disconnect detection via flush errors.
// Application-specific concerns such as topics, replay storage, authentication,
// and pub/sub fan-out intentionally stay outside the core middleware.
package sse
import (
"bufio"
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/utils/v2"
)
var errStreamClosed = errors.New("sse: stream closed")
// New creates a new SSE handler.
func New(config ...Config) fiber.Handler {
cfg := configDefault(config...)
if cfg.Handler == nil {
panic("sse: Handler must not be nil")
}
return func(c fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextEventStream)
c.Set(fiber.HeaderCacheControl, "no-cache")
c.Set(fiber.HeaderConnection, "keep-alive")
c.Set("X-Accel-Buffering", "no")
streamContext := c.Context()
lastEventID := c.Get(fiber.HeaderLastEventID)
c.Abandon()View on GitHub (pinned to a105acad6c)
Solutions
- Select on ctx.Done() in every producer goroutine and stop writing when the context is canceled.
- Treat errStreamClosed as a clean shutdown, not an error to retry or log as fatal.
- Avoid spawning fire-and-forget goroutines tied to a single SSE connection; use a fan-out broker instead.
Example fix
// before
go func() { for e := range events { sse.Write(e) } }()
// after
go func() {
for {
select {
case <-ctx.Done():
return
case e := <-events:
if err := sse.Write(e); err != nil { return }
}
}
}() Defensive patterns
Strategy: try-catch
Try / catch
if err := sse.Write(ev); err != nil {
if errors.Is(err, sse.ErrStreamClosed) /* or the unexported sentinel via errors.Is */ {
return // clean shutdown
}
return err
} Prevention
- Always select on ctx.Done() in producer goroutines.
- Treat stream-closed as a normal exit, not a retryable error.
- Use a fan-out broker instead of per-connection goroutines.
When it happens
Trigger: A goroutine spawned by the SSE handler attempts to write after the client has disconnected or the request context has been canceled.
Common situations: Background workers feeding events into a closed stream; generators that don't watch ctx.Done(); slow producers outliving the connection; reverse proxies timing out idle SSE connections.
Related errors
- timeout or cancel
- shutdown: graceful timeout has been reached, exiting
- shutdown: server is not running
- runtime.Goexit() called in handler or server panic
- fiber: no view engine configured
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/b75b5ddc744698f4.
Report an issue: GitHub.