vitessio/vitess · error
stream send error: %v
Error message
stream send error: %v
What it means
ExecuteStreamFetch in dbconnpool wraps any error returned by the caller-provided callback that processes the first result of a streaming query. The SQL execution itself succeeded, but the consumer's callback (which sends the initial result fields to the client) failed, so the streaming fetch is aborted with this wrapper error preserving the original cause.
Source
Thrown at go/vt/dbconnpool/connection.go:115
flds, err := dbc.Fields()
if err != nil {
return err
}
firstResult := &sqltypes.Result{Fields: flds}
// If the query produced no result set but an OK packet (e.g. a CALL that
// performs DML), carry its RowsAffected/InsertID/Info/SessionStateChanges
// through so the streaming path reports them like the buffered ExecuteFetch
// path does.
if okRes := dbc.StreamOKResult(); okRes != nil {
firstResult.RowsAffected = okRes.RowsAffected
firstResult.InsertID = okRes.InsertID
firstResult.InsertIDChanged = okRes.InsertIDChanged
firstResult.Info = okRes.Info
firstResult.SessionStateChanges = okRes.SessionStateChanges
}
err = callback(firstResult)
if err != nil {
return fmt.Errorf("stream send error: %v", err)
}
// then get all the rows, sending them as we reach a decent packet size
// start with a pre-allocated array of 256 rows capacity
qr := alloc()
byteCount := 0
for {
row, err := dbc.FetchNext(nil)
if err != nil {
dbc.handleError(err)
return err
}
if row == nil {
break
}
qr.Rows = append(qr.Rows, row)
for _, s := range row {
byteCount += s.Len()View on GitHub (pinned to 01a25a7d17)
Solutions
- Inspect the wrapped %v cause: if the underlying error is a broken pipe / client disconnect, it is benign — handle client cancellation upstream instead of treating it as a query failure.
- Ensure the callback honors context cancellation and returns promptly when the caller's context is done.
- Check client-side (e.g. VTGate-to-app) connection stability and timeouts if disconnects are frequent.
- If the callback error is unexpected, add logging in the callback to capture why sending firstResult failed.
Example fix
// before
err = callback(firstResult)
if err != nil {
return fmt.Errorf("stream send error: %v", err)
}
// after (caller side, tolerate client cancellation)
err = callback(firstResult)
if err != nil {
if ctx.Err() != nil {
return ctx.Err() // expected cancellation, not a stream bug
}
return fmt.Errorf("stream send error: %v", err)
} Defensive patterns
Strategy: try-catch
Try / catch
err := poolConn.ExecuteStreamFetch(query, callback, alloc)
if err != nil {
if strings.Contains(err.Error(), "stream send error") && ctx.Err() != nil {
// client cancelled; treat as normal termination
return nil
}
return err
} Prevention
- Always check ctx.Err() in the callback and return early on cancellation
- Log the wrapped cause to distinguish client disconnects from real send failures
- Keep the callback lightweight so it cannot fail on serialization edge cases
When it happens
Trigger: Calling ExecuteStreamFetch (via streamOnce, e.g. from VTGate streaming query paths) with a callback that returns a non-nil error when handling firstResult — e.g. the downstream client connection was closed or the callback failed to serialize/send the initial fields.
Common situations: Client disconnects mid-query so the callback's write to the client fails; serialization errors on the initial result; context cancellation inside the callback during long-running streamed queries.
Related errors
- ErrStreamClosed
- no function closure for Event stream specified
- stream error @ (including the GTID we failed to process) %v:
- send reply error: %v
- can't parse RAND_EVENT: %v, event data: %#v
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/70adf53979ae2226.
Report an issue: GitHub.