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

  1. Read the wrapped cause: Canceled is expected on workflow stop/shutdown; DeadlineExceeded indicates the caller's timeout is too short for the copy volume.
  2. If streams stop unexpectedly, check tablet logs just before the error for shutdown/OOM and verify the workflow wasn't cancelled by another actor.
  3. 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

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


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/bf08cbe27f3f8cd7. Report an issue: GitHub.