vitessio/vitess · info
row stream ended: %w
Error message
row stream ended: %w
What it means
safeSend wraps context cancellation during a VStreamRows send attempt: if the row streamer's context is already done when it tries to send a response, it returns 'row stream ended' with the context error. It signals orderly termination of the row stream, not a data error.
Source
Thrown at go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go:355
buf.Myprintf(" order by ", sqlparser.NewIdentifierCS(rs.plan.Table.Name))
prefix = ""
for _, pk := range rs.pkColumns {
buf.Myprintf("%s%v", prefix, sqlparser.NewIdentifierCI(rs.plan.Table.Fields[pk].Name))
prefix = ", "
}
return buf.String(), nil
}
func (rs *rowStreamer) streamQuery(send func(*binlogdatapb.VStreamRowsResponse) error) error {
throttleResponseRateLimiter := timer.NewRateLimiter(rowStreamertHeartbeatInterval)
defer throttleResponseRateLimiter.Stop()
var sendMu sync.Mutex
safeSend := func(ctx context.Context, r *binlogdatapb.VStreamRowsResponse) error {
sendMu.Lock()
defer sendMu.Unlock()
if err := ctx.Err(); err != nil {
return fmt.Errorf("row stream ended: %w", err)
}
return send(r)
}
// Let's wait until MySQL is in good shape to stream rows
if err := rs.vse.waitForMySQL(rs.ctx, rs.cp, rs.plan.Table.Name); err != nil {
return err
}
var (
gtid string
rotatedLog bool
err error
)
log.Info(fmt.Sprintf("Streaming rows for query: %s\n", rs.sendQuery))
if rs.mode == RowStreamerModeSingleTable {
gtid, rotatedLog, err = rs.conn.streamWithSnapshot(rs.ctx, rs.plan.Table.Name, rs.sendQuery)
if err != nil {
return err
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Read the wrapped cause: Canceled is expected on workflow stop/shutdown; DeadlineExceeded indicates the caller's timeout is too short for the copy volume.
- If streams stop unexpectedly, check tablet logs just before the error for shutdown/OOM and verify the workflow wasn't cancelled by another actor.
- Re-invoke VStream; it resumes from the lastpk/GTID checkpoint.
Defensive patterns
Strategy: retry
Try / catch
err := rowStream(ctx, ...)
if err != nil {
var ctxErr error
if errors.As(err, &ctxErr) && (errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)) {
// orderly stop: log at info, resume from checkpoint
resumeFromCheckpoint(ctx)
}
} Prevention
- Size stream timeouts to table size
- Cancel workflows explicitly and expect this error during teardown
- Keep gRPC connections alive (no aggressive LB idle timeouts)
- Resume from lastpk instead of re-copying
When it happens
Trigger: rs.ctx cancelled/deadline exceeded between events during Stream → streamQuery; the next safeSend call observes ctx.Err() and returns this error.
Common situations: VReplication workflow cancelled or completed, tablet graceful shutdown during copy or streaming phase, caller-side timeout on VStreamRows.
Related errors
- stream ended: %v
- primary key %d refers to non-existent column
- cannot build a row streamer plan for the %s table as a lastp
- row stream send error: %w
- VStreamer is not open
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/bf08cbe27f3f8cd7.
Report an issue: GitHub.