vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vcopier.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vreplication
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"math"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"google.golang.org/protobuf/encoding/prototext"
    29  	"google.golang.org/protobuf/proto"
    30  
    31  	"vitess.io/vitess/go/bytes2"
    32  	"vitess.io/vitess/go/mysql"
    33  	"vitess.io/vitess/go/pools"
    34  	"vitess.io/vitess/go/sqltypes"
    35  	"vitess.io/vitess/go/vt/binlog/binlogplayer"
    36  	"vitess.io/vitess/go/vt/log"
    37  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    38  	querypb "vitess.io/vitess/go/vt/proto/query"
    39  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    40  	"vitess.io/vitess/go/vt/sqlparser"
    41  	"vitess.io/vitess/go/vt/vterrors"
    42  )
    43  
    44  type vcopier struct {
    45  	vr               *vreplicator
    46  	throttlerAppName string
    47  }
    48  
    49  // vcopierCopyTask stores the args and lifecycle hooks of a copy task.
    50  type vcopierCopyTask struct {
    51  	args      *vcopierCopyTaskArgs
    52  	lifecycle *vcopierCopyTaskLifecycle
    53  }
    54  
    55  // vcopierCopyTaskArgs stores the input of a copy task.
    56  type vcopierCopyTaskArgs struct {
    57  	lastpk *querypb.Row
    58  	rows   []*querypb.Row
    59  }
    60  
    61  // vcopierCopyTaskHooks contains callback functions to be triggered as a copy
    62  // task progresses through in-progress phases of its lifecycle.
    63  type vcopierCopyTaskHooks struct {
    64  	fns []func(context.Context, *vcopierCopyTaskArgs) error
    65  }
    66  
    67  // vcopierCopyTaskLifecycle can be used to inject additional behaviors into the
    68  // vcopierCopyTask execution.
    69  //
    70  // It contains two types of hooks. In-progress hooks (simply called "hooks")
    71  // which can be registered before or after various phases of the copy task,
    72  // such as "insert row", "commit", etc. Result hooks are used to register
    73  // callbacks to be triggered when a task is "done" (= canceled, completed,
    74  // failed).
    75  type vcopierCopyTaskLifecycle struct {
    76  	hooks       map[string]*vcopierCopyTaskHooks
    77  	resultHooks *vcopierCopyTaskResultHooks
    78  }
    79  
    80  // vcopierCopyTaskResult contains information about a task that is done.
    81  type vcopierCopyTaskResult struct {
    82  	args      *vcopierCopyTaskArgs
    83  	err       error
    84  	startedAt time.Time
    85  	state     vcopierCopyTaskState
    86  }
    87  
    88  // vcopierCopyTaskHooks contains callback functions to be triggered when a copy
    89  // reaches a "done" state (= canceled, completed, failed).
    90  type vcopierCopyTaskResultHooks struct {
    91  	fns []func(context.Context, *vcopierCopyTaskResult)
    92  }
    93  
    94  // vcopierCopyTaskState marks the states and sub-states that a copy task goes
    95  // through.
    96  //
    97  //  1. Pending
    98  //  2. Begin
    99  //  3. Insert rows
   100  //  4. Insert copy state
   101  //  5. Commit
   102  //  6. One of:
   103  //     - Complete
   104  //     - Cancel
   105  //     - Fail
   106  type vcopierCopyTaskState int
   107  
   108  const (
   109  	vcopierCopyTaskPending vcopierCopyTaskState = iota
   110  	vcopierCopyTaskBegin
   111  	vcopierCopyTaskInsertRows
   112  	vcopierCopyTaskInsertCopyState
   113  	vcopierCopyTaskCommit
   114  	vcopierCopyTaskCancel
   115  	vcopierCopyTaskComplete
   116  	vcopierCopyTaskFail
   117  )
   118  
   119  // vcopierCopyWorkQueue accepts tasks via Enqueue, and distributes those tasks
   120  // concurrently (or synchronously) to internal workers.
   121  type vcopierCopyWorkQueue struct {
   122  	concurrent    bool
   123  	isOpen        bool
   124  	maxDepth      int
   125  	workerFactory func(context.Context) (*vcopierCopyWorker, error)
   126  	workerPool    *pools.ResourcePool
   127  }
   128  
   129  // vcopierCopyWorker will Execute a single task at a time in the calling
   130  // goroutine.
   131  type vcopierCopyWorker struct {
   132  	*vdbClient
   133  	closeDbClient   bool
   134  	copyStateInsert *sqlparser.ParsedQuery
   135  	isOpen          bool
   136  	pkfields        []*querypb.Field
   137  	sqlbuffer       bytes2.Buffer
   138  	tablePlan       *TablePlan
   139  }
   140  
   141  func newVCopier(vr *vreplicator) *vcopier {
   142  	return &vcopier{
   143  		vr:               vr,
   144  		throttlerAppName: vr.throttlerAppName(),
   145  	}
   146  }
   147  
   148  func newVCopierCopyTask(args *vcopierCopyTaskArgs) *vcopierCopyTask {
   149  	return &vcopierCopyTask{
   150  		args:      args,
   151  		lifecycle: newVCopierCopyTaskLifecycle(),
   152  	}
   153  }
   154  
   155  func newVCopierCopyTaskArgs(rows []*querypb.Row, lastpk *querypb.Row) *vcopierCopyTaskArgs {
   156  	return &vcopierCopyTaskArgs{
   157  		rows:   rows,
   158  		lastpk: lastpk,
   159  	}
   160  }
   161  
   162  func newVCopierCopyTaskHooks() *vcopierCopyTaskHooks {
   163  	return &vcopierCopyTaskHooks{
   164  		fns: make([]func(context.Context, *vcopierCopyTaskArgs) error, 0),
   165  	}
   166  }
   167  
   168  func newVCopierCopyTaskLifecycle() *vcopierCopyTaskLifecycle {
   169  	return &vcopierCopyTaskLifecycle{
   170  		hooks:       make(map[string]*vcopierCopyTaskHooks),
   171  		resultHooks: newVCopierCopyTaskResultHooks(),
   172  	}
   173  }
   174  
   175  func newVCopierCopyTaskResult(args *vcopierCopyTaskArgs, startedAt time.Time, state vcopierCopyTaskState, err error) *vcopierCopyTaskResult {
   176  	return &vcopierCopyTaskResult{
   177  		args:      args,
   178  		err:       err,
   179  		startedAt: startedAt,
   180  		state:     state,
   181  	}
   182  }
   183  
   184  func newVCopierCopyTaskResultHooks() *vcopierCopyTaskResultHooks {
   185  	return &vcopierCopyTaskResultHooks{
   186  		fns: make([]func(context.Context, *vcopierCopyTaskResult), 0),
   187  	}
   188  }
   189  
   190  func newVCopierCopyWorkQueue(
   191  	concurrent bool,
   192  	maxDepth int,
   193  	workerFactory func(ctx context.Context) (*vcopierCopyWorker, error),
   194  ) *vcopierCopyWorkQueue {
   195  	maxDepth = int(math.Max(float64(maxDepth), 1))
   196  	return &vcopierCopyWorkQueue{
   197  		concurrent:    concurrent,
   198  		maxDepth:      maxDepth,
   199  		workerFactory: workerFactory,
   200  	}
   201  }
   202  
   203  func newVCopierCopyWorker(
   204  	closeDbClient bool,
   205  	vdbClient *vdbClient,
   206  ) *vcopierCopyWorker {
   207  	return &vcopierCopyWorker{
   208  		closeDbClient: closeDbClient,
   209  		vdbClient:     vdbClient,
   210  	}
   211  }
   212  
   213  // initTablesForCopy (phase 1) identifies the list of tables to be copied and inserts
   214  // them into copy_state. If there are no tables to copy, it explicitly stops
   215  // the stream. Otherwise, the copy phase (phase 2) may think that all tables are copied.
   216  // This will cause us to go into the replication phase (phase 3) without a starting position.
   217  func (vc *vcopier) initTablesForCopy(ctx context.Context) error {
   218  	defer vc.vr.dbClient.Rollback()
   219  
   220  	plan, err := buildReplicatorPlan(vc.vr.source, vc.vr.colInfoMap, nil, vc.vr.stats)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	if err := vc.vr.dbClient.Begin(); err != nil {
   225  		return err
   226  	}
   227  	// Insert the table list only if at least one table matches.
   228  	if len(plan.TargetTables) != 0 {
   229  		var buf strings.Builder
   230  		buf.WriteString("insert into _vt.copy_state(vrepl_id, table_name) values ")
   231  		prefix := ""
   232  		for name := range plan.TargetTables {
   233  			fmt.Fprintf(&buf, "%s(%d, %s)", prefix, vc.vr.id, encodeString(name))
   234  			prefix = ", "
   235  		}
   236  		if _, err := vc.vr.dbClient.Execute(buf.String()); err != nil {
   237  			return err
   238  		}
   239  		if err := vc.vr.setState(binlogplayer.VReplicationCopying, ""); err != nil {
   240  			return err
   241  		}
   242  		if err := vc.vr.insertLog(LogCopyStart, fmt.Sprintf("Copy phase started for %d table(s)",
   243  			len(plan.TargetTables))); err != nil {
   244  			return err
   245  		}
   246  
   247  		if vc.vr.supportsDeferredSecondaryKeys() {
   248  			settings, err := binlogplayer.ReadVRSettings(vc.vr.dbClient, vc.vr.id)
   249  			if err != nil {
   250  				return err
   251  			}
   252  			if settings.DeferSecondaryKeys {
   253  				if err := vc.vr.insertLog(LogCopyStart, fmt.Sprintf("Copy phase temporarily dropping secondary keys for %d table(s)",
   254  					len(plan.TargetTables))); err != nil {
   255  					return err
   256  				}
   257  				for name := range plan.TargetTables {
   258  					if err := vc.vr.stashSecondaryKeys(ctx, name); err != nil {
   259  						return err
   260  					}
   261  				}
   262  				if err := vc.vr.insertLog(LogCopyStart,
   263  					fmt.Sprintf("Copy phase finished dropping secondary keys and saving post copy actions to restore them for %d table(s)",
   264  						len(plan.TargetTables))); err != nil {
   265  					return err
   266  				}
   267  			}
   268  		}
   269  	} else {
   270  		if err := vc.vr.setState(binlogplayer.BlpStopped, "There is nothing to replicate"); err != nil {
   271  			return err
   272  		}
   273  	}
   274  	return vc.vr.dbClient.Commit()
   275  }
   276  
   277  // copyNext performs a multi-step process on each iteration.
   278  // Step 1: catchup: During this step, it replicates from the source from the last position.
   279  // This is a partial replication: events are applied only to tables or subsets of tables
   280  // that have already been copied. This goes on until replication catches up.
   281  // Step 2: Start streaming. This returns the initial field info along with the GTID
   282  // as of which the snapshot is being streamed.
   283  // Step 3: fastForward: The target is fast-forwarded to the GTID obtained. This should
   284  // be quick because we were mostly caught up as of step 1. This ensures that the
   285  // snapshot of the rows are consistent with the position where the target stopped.
   286  // Step 4: copy rows: Copy the next set of rows from the stream that was started in Step 2.
   287  // This goes on until all rows are copied, or a timeout. In both cases, copyNext
   288  // returns, and the replicator decides whether to invoke copyNext again, or to
   289  // go to the next phase if all the copying is done.
   290  // Steps 2, 3 and 4 are performed by copyTable.
   291  // copyNext also builds the copyState metadata that contains the tables and their last
   292  // primary key that was copied. A nil Result means that nothing has been copied.
   293  // A table that was fully copied is removed from copyState.
   294  func (vc *vcopier) copyNext(ctx context.Context, settings binlogplayer.VRSettings) error {
   295  	qr, err := vc.vr.dbClient.Execute(fmt.Sprintf("select table_name, lastpk from _vt.copy_state where vrepl_id = %d and id in (select max(id) from _vt.copy_state group by vrepl_id, table_name)", vc.vr.id))
   296  	if err != nil {
   297  		return err
   298  	}
   299  	var tableToCopy string
   300  	copyState := make(map[string]*sqltypes.Result)
   301  	for _, row := range qr.Rows {
   302  		tableName := row[0].ToString()
   303  		lastpk := row[1].ToString()
   304  		if tableToCopy == "" {
   305  			tableToCopy = tableName
   306  		}
   307  		copyState[tableName] = nil
   308  		if lastpk != "" {
   309  			var r querypb.QueryResult
   310  			if err := prototext.Unmarshal([]byte(lastpk), &r); err != nil {
   311  				return err
   312  			}
   313  			copyState[tableName] = sqltypes.Proto3ToResult(&r)
   314  		}
   315  	}
   316  	if len(copyState) == 0 {
   317  		return fmt.Errorf("unexpected: there are no tables to copy")
   318  	}
   319  	if err := vc.catchup(ctx, copyState); err != nil {
   320  		return err
   321  	}
   322  	return vc.copyTable(ctx, tableToCopy, copyState)
   323  }
   324  
   325  // catchup replays events to the subset of the tables that have been copied
   326  // until replication is caught up. In order to stop, the seconds behind primary has
   327  // to fall below replicationLagTolerance.
   328  func (vc *vcopier) catchup(ctx context.Context, copyState map[string]*sqltypes.Result) error {
   329  	ctx, cancel := context.WithCancel(ctx)
   330  	defer cancel()
   331  	defer vc.vr.stats.PhaseTimings.Record("catchup", time.Now())
   332  
   333  	settings, err := binlogplayer.ReadVRSettings(vc.vr.dbClient, vc.vr.id)
   334  	if err != nil {
   335  		return err
   336  	}
   337  	// If there's no start position, it means we're copying the
   338  	// first table. So, there's nothing to catch up to.
   339  	if settings.StartPos.IsZero() {
   340  		return nil
   341  	}
   342  
   343  	// Start vreplication.
   344  	errch := make(chan error, 1)
   345  	go func() {
   346  		errch <- newVPlayer(vc.vr, settings, copyState, mysql.Position{}, "catchup").play(ctx)
   347  	}()
   348  
   349  	// Wait for catchup.
   350  	tkr := time.NewTicker(waitRetryTime)
   351  	defer tkr.Stop()
   352  	seconds := int64(replicaLagTolerance / time.Second)
   353  	for {
   354  		sbm := vc.vr.stats.ReplicationLagSeconds.Get()
   355  		if sbm < seconds {
   356  			cancel()
   357  			// Make sure vplayer returns before returning.
   358  			<-errch
   359  			return nil
   360  		}
   361  		select {
   362  		case err := <-errch:
   363  			if err != nil {
   364  				return err
   365  			}
   366  			return io.EOF
   367  		case <-ctx.Done():
   368  			// Make sure vplayer returns before returning.
   369  			<-errch
   370  			return io.EOF
   371  		case <-tkr.C:
   372  		}
   373  	}
   374  }
   375  
   376  // copyTable performs the synchronized copy of the next set of rows from
   377  // the current table being copied. Each packet received is transactionally
   378  // committed with the lastpk. This allows for consistent resumability.
   379  func (vc *vcopier) copyTable(ctx context.Context, tableName string, copyState map[string]*sqltypes.Result) error {
   380  	defer vc.vr.dbClient.Rollback()
   381  	defer vc.vr.stats.PhaseTimings.Record("copy", time.Now())
   382  	defer vc.vr.stats.CopyLoopCount.Add(1)
   383  
   384  	log.Infof("Copying table %s, lastpk: %v", tableName, copyState[tableName])
   385  
   386  	plan, err := buildReplicatorPlan(vc.vr.source, vc.vr.colInfoMap, nil, vc.vr.stats)
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	initialPlan, ok := plan.TargetTables[tableName]
   392  	if !ok {
   393  		return fmt.Errorf("plan not found for table: %s, current plans are: %#v", tableName, plan.TargetTables)
   394  	}
   395  
   396  	ctx, cancel := context.WithTimeout(ctx, copyPhaseDuration)
   397  	defer cancel()
   398  
   399  	var lastpkpb *querypb.QueryResult
   400  	if lastpkqr := copyState[tableName]; lastpkqr != nil {
   401  		lastpkpb = sqltypes.ResultToProto3(lastpkqr)
   402  	}
   403  
   404  	rowsCopiedTicker := time.NewTicker(rowsCopiedUpdateInterval)
   405  	defer rowsCopiedTicker.Stop()
   406  	copyStateGCTicker := time.NewTicker(copyStateGCInterval)
   407  	defer copyStateGCTicker.Stop()
   408  
   409  	parallelism := int(math.Max(1, float64(vreplicationParallelInsertWorkers)))
   410  	copyWorkerFactory := vc.newCopyWorkerFactory(parallelism)
   411  	copyWorkQueue := vc.newCopyWorkQueue(parallelism, copyWorkerFactory)
   412  	defer copyWorkQueue.close()
   413  
   414  	// Allocate a result channel to collect results from tasks.
   415  	resultCh := make(chan *vcopierCopyTaskResult, parallelism*4)
   416  	defer close(resultCh)
   417  
   418  	var lastpk *querypb.Row
   419  	var pkfields []*querypb.Field
   420  
   421  	// Use this for task sequencing.
   422  	var prevCh <-chan *vcopierCopyTaskResult
   423  
   424  	serr := vc.vr.sourceVStreamer.VStreamRows(ctx, initialPlan.SendRule.Filter, lastpkpb, func(rows *binlogdatapb.VStreamRowsResponse) error {
   425  		for {
   426  			select {
   427  			case <-rowsCopiedTicker.C:
   428  				update := binlogplayer.GenerateUpdateRowsCopied(vc.vr.id, vc.vr.stats.CopyRowCount.Get())
   429  				_, _ = vc.vr.dbClient.Execute(update)
   430  			case <-ctx.Done():
   431  				return io.EOF
   432  			default:
   433  			}
   434  			select {
   435  			case <-copyStateGCTicker.C:
   436  				// Garbage collect older copy_state rows:
   437  				//   - Using a goroutine so that we are not blocking the copy flow
   438  				//   - Using a new connection so that we do not change the transactional behavior of the copy itself
   439  				// This helps to ensure that the table does not grow too large and the
   440  				// number of rows does not have a big impact on the queries used for
   441  				// the workflow.
   442  				go func() {
   443  					gcQuery := fmt.Sprintf("delete from _vt.copy_state where vrepl_id = %d and table_name = %s and id < (select maxid from (select max(id) as maxid from _vt.copy_state where vrepl_id = %d and table_name = %s) as depsel)",
   444  						vc.vr.id, encodeString(tableName), vc.vr.id, encodeString(tableName))
   445  					dbClient := vc.vr.vre.getDBClient(false)
   446  					if err := dbClient.Connect(); err != nil {
   447  						log.Errorf("Error while garbage collecting older copy_state rows, could not connect to database: %v", err)
   448  						return
   449  					}
   450  					defer dbClient.Close()
   451  					if _, err := dbClient.ExecuteFetch(gcQuery, -1); err != nil {
   452  						log.Errorf("Error while garbage collecting older copy_state rows with query %q: %v", gcQuery, err)
   453  					}
   454  				}()
   455  			case <-ctx.Done():
   456  				return io.EOF
   457  			default:
   458  			}
   459  			if rows.Throttled {
   460  				_ = vc.vr.updateTimeThrottled(RowStreamerComponentName)
   461  				return nil
   462  			}
   463  			if rows.Heartbeat {
   464  				_ = vc.vr.updateHeartbeatTime(time.Now().Unix())
   465  				return nil
   466  			}
   467  			// verify throttler is happy, otherwise keep looping
   468  			if vc.vr.vre.throttlerClient.ThrottleCheckOKOrWaitAppName(ctx, vc.throttlerAppName) {
   469  				break // out of 'for' loop
   470  			} else { // we're throttled
   471  				_ = vc.vr.updateTimeThrottled(VCopierComponentName)
   472  			}
   473  		}
   474  		if !copyWorkQueue.isOpen {
   475  			if len(rows.Fields) == 0 {
   476  				return fmt.Errorf("expecting field event first, got: %v", rows)
   477  			}
   478  			if err := vc.fastForward(ctx, copyState, rows.Gtid); err != nil {
   479  				return err
   480  			}
   481  			fieldEvent := &binlogdatapb.FieldEvent{
   482  				TableName: initialPlan.SendRule.Match,
   483  			}
   484  			fieldEvent.Fields = append(fieldEvent.Fields, rows.Fields...)
   485  			tablePlan, err := plan.buildExecutionPlan(fieldEvent)
   486  			if err != nil {
   487  				return err
   488  			}
   489  			pkfields = append(pkfields, rows.Pkfields...)
   490  			buf := sqlparser.NewTrackedBuffer(nil)
   491  			buf.Myprintf(
   492  				"insert into _vt.copy_state (lastpk, vrepl_id, table_name) values (%a, %s, %s)", ":lastpk",
   493  				strconv.Itoa(int(vc.vr.id)),
   494  				encodeString(tableName))
   495  			addLatestCopyState := buf.ParsedQuery()
   496  			copyWorkQueue.open(addLatestCopyState, pkfields, tablePlan)
   497  		}
   498  		if len(rows.Rows) == 0 {
   499  			return nil
   500  		}
   501  
   502  		// Clone rows, since pointer values will change while async work is
   503  		// happening. Can skip this when there's no parallelism.
   504  		if parallelism > 1 {
   505  			rows = proto.Clone(rows).(*binlogdatapb.VStreamRowsResponse)
   506  		}
   507  
   508  		// Prepare a vcopierCopyTask for the current batch of work.
   509  		// TODO(maxeng) see if using a pre-allocated pool will speed things up.
   510  		currCh := make(chan *vcopierCopyTaskResult, 1)
   511  		currT := newVCopierCopyTask(newVCopierCopyTaskArgs(rows.Rows, rows.Lastpk))
   512  
   513  		// Send result to the global resultCh and currCh. resultCh is used by
   514  		// the loop to return results to VStreamRows. currCh will be used to
   515  		// sequence the start of the nextT.
   516  		currT.lifecycle.onResult().sendTo(currCh)
   517  		currT.lifecycle.onResult().sendTo(resultCh)
   518  
   519  		// Use prevCh to Sequence the prevT with the currT so that:
   520  		// * The prevT is completed before we begin updating
   521  		//   _vt.copy_state for currT.
   522  		// * If prevT fails or is canceled, the current task is
   523  		//   canceled.
   524  		// prevCh is nil only for the first task in the vcopier run.
   525  		if prevCh != nil {
   526  			// prevT publishes to prevCh, and currT is the only thing that can
   527  			// consume from prevCh. If prevT is already done, then prevCh will
   528  			// have a value in it. If prevT isn't yet done, then prevCh will
   529  			// have a value later. Either way, AwaitCompletion should
   530  			// eventually get a value, unless there is a context expiry.
   531  			currT.lifecycle.before(vcopierCopyTaskInsertCopyState).awaitCompletion(prevCh)
   532  		}
   533  
   534  		// Store currCh in prevCh. The nextT will use this for sequencing.
   535  		prevCh = currCh
   536  
   537  		// Update stats after task is done.
   538  		currT.lifecycle.onResult().do(func(_ context.Context, result *vcopierCopyTaskResult) {
   539  			if result.state == vcopierCopyTaskFail {
   540  				vc.vr.stats.ErrorCounts.Add([]string{"Copy"}, 1)
   541  			}
   542  			if result.state == vcopierCopyTaskComplete {
   543  				vc.vr.stats.CopyRowCount.Add(int64(len(result.args.rows)))
   544  				vc.vr.stats.QueryCount.Add("copy", 1)
   545  				vc.vr.stats.TableCopyRowCounts.Add(tableName, int64(len(result.args.rows)))
   546  				vc.vr.stats.TableCopyTimings.Add(tableName, time.Since(result.startedAt))
   547  			}
   548  		})
   549  
   550  		if err := copyWorkQueue.enqueue(ctx, currT); err != nil {
   551  			log.Warningf("failed to enqueue task in workflow %s: %s", vc.vr.WorkflowName, err.Error())
   552  			return err
   553  		}
   554  
   555  		// When async execution is not enabled, a done task will be available
   556  		// in the resultCh after each Enqueue, unless there was a queue state
   557  		// error (e.g. couldn't obtain a worker from pool).
   558  		//
   559  		// When async execution is enabled, results will show up in the channel
   560  		// eventually, possibly in a subsequent VStreamRows loop. It's still
   561  		// a good idea to check this channel on every pass so that:
   562  		//
   563  		// * resultCh doesn't fill up. If it does fill up then tasks won't be
   564  		//   able to add their results to the channel, and progress in this
   565  		//   goroutine will be blocked.
   566  		// * We keep lastpk up-to-date.
   567  		select {
   568  		case result := <-resultCh:
   569  			if result != nil {
   570  				switch result.state {
   571  				case vcopierCopyTaskCancel:
   572  					log.Warningf("task was canceled in workflow %s: %v", vc.vr.WorkflowName, result.err)
   573  					return io.EOF
   574  				case vcopierCopyTaskComplete:
   575  					// Collect lastpk. Needed for logging at the end.
   576  					lastpk = result.args.lastpk
   577  				case vcopierCopyTaskFail:
   578  					return vterrors.Wrapf(result.err, "task error")
   579  				}
   580  			} else {
   581  				return io.EOF
   582  			}
   583  		default:
   584  		}
   585  
   586  		return nil
   587  	})
   588  
   589  	// Close the work queue. This will prevent new tasks from being enqueued,
   590  	// and will wait until all workers are returned to the worker pool.
   591  	copyWorkQueue.close()
   592  
   593  	// When tasks are executed async, there may be tasks that complete (or fail)
   594  	// after the last VStreamRows callback exits. Get the lastpk from completed
   595  	// tasks, or errors from failed ones.
   596  	var empty bool
   597  	var terrs []error
   598  	for !empty {
   599  		select {
   600  		case result := <-resultCh:
   601  			switch result.state {
   602  			case vcopierCopyTaskCancel:
   603  				// A task cancelation probably indicates an expired context due
   604  				// to a PlannedReparentShard or elapsed copy phase duration,
   605  				// neither of which are error conditions.
   606  			case vcopierCopyTaskComplete:
   607  				// Get the latest lastpk, purely for logging purposes.
   608  				lastpk = result.args.lastpk
   609  			case vcopierCopyTaskFail:
   610  				// Aggregate non-nil errors.
   611  				terrs = append(terrs, result.err)
   612  			}
   613  		default:
   614  			empty = true
   615  		}
   616  	}
   617  	if len(terrs) > 0 {
   618  		terr := vterrors.Aggregate(terrs)
   619  		log.Warningf("task error in workflow %s: %v", vc.vr.WorkflowName, terr)
   620  		return vterrors.Wrapf(terr, "task error")
   621  	}
   622  
   623  	// Get the last committed pk into a loggable form.
   624  	lastpkbuf, merr := prototext.Marshal(&querypb.QueryResult{
   625  		Fields: pkfields,
   626  		Rows:   []*querypb.Row{lastpk},
   627  	})
   628  	if merr != nil {
   629  		return fmt.Errorf("failed to marshal pk fields and value into query result: %s", merr.Error())
   630  	}
   631  	lastpkbv := map[string]*querypb.BindVariable{
   632  		"lastpk": {
   633  			Type:  sqltypes.VarBinary,
   634  			Value: lastpkbuf,
   635  		},
   636  	}
   637  
   638  	// A context expiration was probably caused by a PlannedReparentShard or an
   639  	// elapsed copy phase duration. Those are normal, non-error interruptions
   640  	// of a copy phase.
   641  	select {
   642  	case <-ctx.Done():
   643  		log.Infof("Copy of %v stopped at lastpk: %v", tableName, lastpkbv)
   644  		return nil
   645  	default:
   646  	}
   647  	if serr != nil {
   648  		return serr
   649  	}
   650  
   651  	// Perform any post copy actions
   652  	if err := vc.vr.execPostCopyActions(ctx, tableName); err != nil {
   653  		return vterrors.Wrapf(err, "failed to execute post copy actions for table %q", tableName)
   654  	}
   655  
   656  	log.Infof("Copy of %v finished at lastpk: %v", tableName, lastpkbv)
   657  	buf := sqlparser.NewTrackedBuffer(nil)
   658  	buf.Myprintf(
   659  		"delete cs, pca from %s as cs left join %s as pca on cs.vrepl_id=pca.vrepl_id and cs.table_name=pca.table_name where cs.vrepl_id=%d and cs.table_name=%s",
   660  		copyStateTableName, postCopyActionTableName,
   661  		vc.vr.id, encodeString(tableName),
   662  	)
   663  	if _, err := vc.vr.dbClient.Execute(buf.String()); err != nil {
   664  		return err
   665  	}
   666  
   667  	return nil
   668  }
   669  
   670  func (vc *vcopier) fastForward(ctx context.Context, copyState map[string]*sqltypes.Result, gtid string) error {
   671  	defer vc.vr.stats.PhaseTimings.Record("fastforward", time.Now())
   672  	pos, err := mysql.DecodePosition(gtid)
   673  	if err != nil {
   674  		return err
   675  	}
   676  	settings, err := binlogplayer.ReadVRSettings(vc.vr.dbClient, vc.vr.id)
   677  	if err != nil {
   678  		return err
   679  	}
   680  	if settings.StartPos.IsZero() {
   681  		update := binlogplayer.GenerateUpdatePos(vc.vr.id, pos, time.Now().Unix(), 0, vc.vr.stats.CopyRowCount.Get(), vreplicationStoreCompressedGTID)
   682  		_, err := vc.vr.dbClient.Execute(update)
   683  		return err
   684  	}
   685  	return newVPlayer(vc.vr, settings, copyState, pos, "fastforward").play(ctx)
   686  }
   687  
   688  func (vc *vcopier) newCopyWorkQueue(
   689  	parallelism int,
   690  	workerFactory func(context.Context) (*vcopierCopyWorker, error),
   691  ) *vcopierCopyWorkQueue {
   692  	concurrent := parallelism > 1
   693  	return newVCopierCopyWorkQueue(concurrent, parallelism, workerFactory)
   694  }
   695  
   696  func (vc *vcopier) newCopyWorkerFactory(parallelism int) func(context.Context) (*vcopierCopyWorker, error) {
   697  	if parallelism > 1 {
   698  		return func(ctx context.Context) (*vcopierCopyWorker, error) {
   699  			dbClient, err := vc.vr.newClientConnection(ctx)
   700  			if err != nil {
   701  				return nil, fmt.Errorf("failed to create new db client: %s", err.Error())
   702  			}
   703  			return newVCopierCopyWorker(
   704  				true, /* close db client */
   705  				dbClient,
   706  			), nil
   707  		}
   708  	}
   709  	return func(_ context.Context) (*vcopierCopyWorker, error) {
   710  		return newVCopierCopyWorker(
   711  			false, /* close db client */
   712  			vc.vr.dbClient,
   713  		), nil
   714  	}
   715  }
   716  
   717  // close waits for all workers to be returned to the worker pool.
   718  func (vcq *vcopierCopyWorkQueue) close() {
   719  	if !vcq.isOpen {
   720  		return
   721  	}
   722  	vcq.isOpen = false
   723  	vcq.workerPool.Close()
   724  }
   725  
   726  // enqueue a new copy task. This will obtain a worker from the pool, execute
   727  // the task with that worker, and afterwards return the worker to the pool. If
   728  // vcopierCopyWorkQueue is configured to operate concurrently, the task will be
   729  // executed in a separate goroutine. Otherwise the task will be executed in the
   730  // calling goroutine.
   731  func (vcq *vcopierCopyWorkQueue) enqueue(ctx context.Context, currT *vcopierCopyTask) error {
   732  	if !vcq.isOpen {
   733  		return fmt.Errorf("work queue is not open")
   734  	}
   735  
   736  	// Get a handle on an unused worker.
   737  	poolH, err := vcq.workerPool.Get(ctx, nil)
   738  	if err != nil {
   739  		return fmt.Errorf("failed to get a worker from pool: %s", err.Error())
   740  	}
   741  
   742  	currW, ok := poolH.(*vcopierCopyWorker)
   743  	if !ok {
   744  		return fmt.Errorf("failed to cast pool resource to *vcopierCopyWorker")
   745  	}
   746  
   747  	execute := func(task *vcopierCopyTask) {
   748  		currW.execute(ctx, task)
   749  		vcq.workerPool.Put(poolH)
   750  	}
   751  
   752  	// If the work queue is configured to work concurrently, execute the task
   753  	// in a separate goroutine. Otherwise execute the task in the calling
   754  	// goroutine.
   755  	if vcq.concurrent {
   756  		go execute(currT)
   757  	} else {
   758  		execute(currT)
   759  	}
   760  
   761  	return nil
   762  }
   763  
   764  // open the work queue. The provided arguments are used to generate
   765  // statements for inserting rows and copy state.
   766  func (vcq *vcopierCopyWorkQueue) open(
   767  	copyStateInsert *sqlparser.ParsedQuery,
   768  	pkfields []*querypb.Field,
   769  	tablePlan *TablePlan,
   770  ) {
   771  	if vcq.isOpen {
   772  		return
   773  	}
   774  
   775  	poolCapacity := int(math.Max(float64(vcq.maxDepth), 1))
   776  	vcq.workerPool = pools.NewResourcePool(
   777  		/* factory */
   778  		func(ctx context.Context) (pools.Resource, error) {
   779  			worker, err := vcq.workerFactory(ctx)
   780  			if err != nil {
   781  				return nil, fmt.Errorf(
   782  					"failed to create vcopier worker: %s",
   783  					err.Error(),
   784  				)
   785  			}
   786  			worker.open(copyStateInsert, pkfields, tablePlan)
   787  			return worker, nil
   788  		},
   789  		poolCapacity, /* initial capacity */
   790  		poolCapacity, /* max capacity */
   791  		0,            /* idle timeout */
   792  		0,            /* max lifetime */
   793  		nil,          /* log wait */
   794  		nil,          /* refresh check */
   795  		0,            /* refresh interval */
   796  	)
   797  
   798  	vcq.isOpen = true
   799  }
   800  
   801  // after returns a vcopierCopyTaskHooks that can be used to register callbacks
   802  // to be triggered after the specified vcopierCopyTaskState.
   803  func (vtl *vcopierCopyTaskLifecycle) after(state vcopierCopyTaskState) *vcopierCopyTaskHooks {
   804  	key := "after:" + state.String()
   805  	if _, ok := vtl.hooks[key]; !ok {
   806  		vtl.hooks[key] = newVCopierCopyTaskHooks()
   807  	}
   808  	return vtl.hooks[key]
   809  }
   810  
   811  // before returns a vcopierCopyTaskHooks that can be used to register callbacks
   812  // to be triggered before the the specified vcopierCopyTaskState.
   813  func (vtl *vcopierCopyTaskLifecycle) before(state vcopierCopyTaskState) *vcopierCopyTaskHooks {
   814  	key := "before:" + state.String()
   815  	if _, ok := vtl.hooks[key]; !ok {
   816  		vtl.hooks[key] = newVCopierCopyTaskHooks()
   817  	}
   818  	return vtl.hooks[key]
   819  }
   820  
   821  // onResult returns a vcopierCopyTaskResultHooks that can be used to register
   822  // callbacks to be triggered when a task reaches a "done" state (= canceled,
   823  // completed, failed).
   824  func (vtl *vcopierCopyTaskLifecycle) onResult() *vcopierCopyTaskResultHooks {
   825  	return vtl.resultHooks
   826  }
   827  
   828  // tryAdvance is a convenient way of wrapping up lifecycle hooks with task
   829  // execution steps. E.g.:
   830  //
   831  //	if _, err := task.lifecycle.before(nextState).notify(ctx, args); err != nil {
   832  //		return err
   833  //	}
   834  //	if _, err := fn(ctx, args); err != nil {
   835  //		return err
   836  //	}
   837  //	if _, err := task.lifecycle.after(nextState).notify(ctx, args); err != nil {
   838  //		return err
   839  //	}
   840  //
   841  // Is roughly equivalent to:
   842  //
   843  //	if _, err := task.Lifecycle.tryAdvance(ctx, args, nextState, fn); err != nil {
   844  //	  return err
   845  //	}
   846  func (vtl *vcopierCopyTaskLifecycle) tryAdvance(
   847  	ctx context.Context,
   848  	args *vcopierCopyTaskArgs,
   849  	nextState vcopierCopyTaskState,
   850  	fn func(context.Context, *vcopierCopyTaskArgs) error,
   851  ) (vcopierCopyTaskState, error) {
   852  	var err error
   853  	newState := nextState
   854  
   855  	if err = vtl.before(nextState).notify(ctx, args); err != nil {
   856  		goto END
   857  	}
   858  	if err = fn(ctx, args); err != nil {
   859  		goto END
   860  	}
   861  	if err = vtl.after(nextState).notify(ctx, args); err != nil {
   862  		goto END
   863  	}
   864  
   865  END:
   866  	if err != nil {
   867  		newState = vcopierCopyTaskFail
   868  		if vterrors.Code(err) == vtrpcpb.Code_CANCELED {
   869  			newState = vcopierCopyTaskCancel
   870  		}
   871  	}
   872  
   873  	return newState, err
   874  }
   875  
   876  // do registers a callback with the vcopierCopyTaskResultHooks, to be triggered
   877  // when a task reaches a "done" state (= canceled, completed, failed).
   878  func (vrh *vcopierCopyTaskResultHooks) do(fn func(context.Context, *vcopierCopyTaskResult)) {
   879  	vrh.fns = append(vrh.fns, fn)
   880  }
   881  
   882  // notify triggers all callbacks registered with this vcopierCopyTaskResultHooks.
   883  func (vrh *vcopierCopyTaskResultHooks) notify(ctx context.Context, result *vcopierCopyTaskResult) {
   884  	for _, fn := range vrh.fns {
   885  		fn(ctx, result)
   886  	}
   887  }
   888  
   889  // sendTo registers a hook that accepts a result and sends the result to the
   890  // provided channel. E.g.:
   891  //
   892  //	resultCh := make(chan *vcopierCopyTaskResult, 1)
   893  //	task.lifecycle.onResult().sendTo(resultCh)
   894  //	defer func() {
   895  //	  result := <-resultCh
   896  //	}()
   897  func (vrh *vcopierCopyTaskResultHooks) sendTo(ch chan<- *vcopierCopyTaskResult) {
   898  	vrh.do(func(ctx context.Context, result *vcopierCopyTaskResult) {
   899  		select {
   900  		case ch <- result:
   901  		case <-ctx.Done():
   902  			// Failure to send the result to the consumer on the other side of
   903  			// the channel before context expires will have the following
   904  			// consequences:
   905  			//
   906  			// * Subsequent tasks waiting for this task to complete won't run.
   907  			//   That's OK. They won't hang waiting on the channel because,
   908  			//   like this task they respond to context expiration.
   909  			// * The outermost loop managing task execution may not know that
   910  			//   this task failed or succeeded.
   911  			//   - In the case that this task succeeded, statistics and logging
   912  			//     will not indicate that this task completed. That's not great,
   913  			//     but shouldn't negatively impact the integrity of data or the
   914  			//     copy workflow because the current state has been persisted
   915  			//     to the database.
   916  			//   - In the case that this task failed, there should be no adverse
   917  			//     impact: the outermost loop handles context expiration by
   918  			//     stopping the copy phase without completing it.
   919  		}
   920  	})
   921  }
   922  
   923  // awaitCompletion registers a callback that returns an error unless the
   924  // provided chan produces a vcopierTaskResult in a complete state.
   925  //
   926  // This is useful for sequencing vcopierCopyTasks, e.g.:
   927  //
   928  //	resultCh := make(chan *vcopierCopyTaskResult, 1)
   929  //	prevT.lifecycle.onResult().sendTo(resultCh)
   930  //	currT.Lifecycle.before(vcopierCopyTaskInsertCopyState).awaitCompletion(resultCh)
   931  func (vth *vcopierCopyTaskHooks) awaitCompletion(resultCh <-chan *vcopierCopyTaskResult) {
   932  	vth.do(func(ctx context.Context, args *vcopierCopyTaskArgs) error {
   933  		select {
   934  		case result := <-resultCh:
   935  			if result == nil {
   936  				return fmt.Errorf("channel was closed before a result received")
   937  			}
   938  			if !vcopierCopyTaskStateIsDone(result.state) {
   939  				return fmt.Errorf("received result is not done")
   940  			}
   941  			if result.state != vcopierCopyTaskComplete {
   942  				return fmt.Errorf("received result is not complete")
   943  			}
   944  			return nil
   945  		case <-ctx.Done():
   946  			// A context expiration probably indicates a PlannedReparentShard
   947  			// or an elapsed copy phase duration. Those aren't treated as error
   948  			// conditions, but we'll return the context error here anyway.
   949  			//
   950  			// Task execution will detect the presence of the error, mark this
   951  			// task canceled, and abort. Subsequent tasks won't execute because
   952  			// this task didn't complete.
   953  			return vterrors.Errorf(vtrpcpb.Code_CANCELED, "context has expired")
   954  		}
   955  	})
   956  }
   957  
   958  // do registers a callback with the vcopierCopyTaskResultHooks, to be triggered
   959  // before or after a user-specified state.
   960  func (vth *vcopierCopyTaskHooks) do(fn func(context.Context, *vcopierCopyTaskArgs) error) {
   961  	vth.fns = append(vth.fns, fn)
   962  }
   963  
   964  // notify triggers all callbacks registered with this vcopierCopyTaskHooks.
   965  func (vth *vcopierCopyTaskHooks) notify(ctx context.Context, args *vcopierCopyTaskArgs) error {
   966  	for _, fn := range vth.fns {
   967  		if err := fn(ctx, args); err != nil {
   968  			return err
   969  		}
   970  	}
   971  	return nil
   972  }
   973  
   974  func (vts vcopierCopyTaskState) String() string {
   975  	switch vts {
   976  	case vcopierCopyTaskPending:
   977  		return "pending"
   978  	case vcopierCopyTaskBegin:
   979  		return "begin"
   980  	case vcopierCopyTaskInsertRows:
   981  		return "insert-rows"
   982  	case vcopierCopyTaskInsertCopyState:
   983  		return "insert-copy-state"
   984  	case vcopierCopyTaskCommit:
   985  		return "commit"
   986  	case vcopierCopyTaskCancel:
   987  		return "done:cancel"
   988  	case vcopierCopyTaskComplete:
   989  		return "done:complete"
   990  	case vcopierCopyTaskFail:
   991  		return "done:fail"
   992  	}
   993  
   994  	return fmt.Sprintf("undefined(%d)", int(vts))
   995  }
   996  
   997  // ApplySetting implements pools.Resource.
   998  func (vbc *vcopierCopyWorker) ApplySetting(context.Context, *pools.Setting) error {
   999  	return vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "[BUG] vcopierCopyWorker does not implement ApplySetting")
  1000  }
  1001  
  1002  // Close implements pool.Resource.
  1003  func (vbc *vcopierCopyWorker) Close() {
  1004  	if !vbc.isOpen {
  1005  		return
  1006  	}
  1007  
  1008  	vbc.isOpen = false
  1009  	if vbc.closeDbClient {
  1010  		vbc.vdbClient.Close()
  1011  	}
  1012  }
  1013  
  1014  // Expired implements pools.Resource.
  1015  func (vbc *vcopierCopyWorker) Expired(time.Duration) bool {
  1016  	return false
  1017  }
  1018  
  1019  // IsSameSetting implements pools.Resource.
  1020  func (vbc *vcopierCopyWorker) IsSameSetting(string) bool {
  1021  	return true
  1022  }
  1023  
  1024  // IsSettingApplied implements pools.Resource.
  1025  func (vbc *vcopierCopyWorker) IsSettingApplied() bool {
  1026  	return false
  1027  }
  1028  
  1029  // ResetSetting implements pools.Resource.
  1030  func (vbc *vcopierCopyWorker) ResetSetting(context.Context) error {
  1031  	return vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "[BUG] vcopierCopyWorker does not implement ResetSetting")
  1032  }
  1033  
  1034  // execute advances a task through each state until it is done (= canceled,
  1035  // completed, failed).
  1036  func (vbc *vcopierCopyWorker) execute(ctx context.Context, task *vcopierCopyTask) *vcopierCopyTaskResult {
  1037  	startedAt := time.Now()
  1038  	state := vcopierCopyTaskPending
  1039  
  1040  	var err error
  1041  
  1042  	// As long as the current state is not done, keep trying to advance to the
  1043  	// next state.
  1044  	for !vcopierCopyTaskStateIsDone(state) {
  1045  		// Get the next state that we want to advance to.
  1046  		nextState := vcopierCopyTaskGetNextState(state)
  1047  
  1048  		var advanceFn func(context.Context, *vcopierCopyTaskArgs) error
  1049  
  1050  		// Get the advanceFn to use to advance the task to the nextState.
  1051  		switch nextState {
  1052  		case vcopierCopyTaskBegin:
  1053  			advanceFn = func(context.Context, *vcopierCopyTaskArgs) error {
  1054  				// Rollback to make sure we're in a clean state.
  1055  				if err := vbc.vdbClient.Rollback(); err != nil {
  1056  					return vterrors.Wrapf(err, "failed to rollback")
  1057  				}
  1058  				// Begin transaction.
  1059  				if err := vbc.vdbClient.Begin(); err != nil {
  1060  					return vterrors.Wrapf(err, "failed to start transaction")
  1061  				}
  1062  				return nil
  1063  			}
  1064  		case vcopierCopyTaskInsertRows:
  1065  			advanceFn = func(ctx context.Context, args *vcopierCopyTaskArgs) error {
  1066  				if _, err := vbc.insertRows(ctx, args.rows); err != nil {
  1067  					return vterrors.Wrapf(err, "failed inserting rows")
  1068  				}
  1069  				return nil
  1070  			}
  1071  		case vcopierCopyTaskInsertCopyState:
  1072  			advanceFn = func(ctx context.Context, args *vcopierCopyTaskArgs) error {
  1073  				if err := vbc.insertCopyState(ctx, args.lastpk); err != nil {
  1074  					return vterrors.Wrapf(err, "error updating _vt.copy_state")
  1075  				}
  1076  				return nil
  1077  			}
  1078  		case vcopierCopyTaskCommit:
  1079  			advanceFn = func(context.Context, *vcopierCopyTaskArgs) error {
  1080  				// Commit.
  1081  				if err := vbc.vdbClient.Commit(); err != nil {
  1082  					return vterrors.Wrapf(err, "error commiting transaction")
  1083  				}
  1084  				return nil
  1085  			}
  1086  		case vcopierCopyTaskComplete:
  1087  			advanceFn = func(context.Context, *vcopierCopyTaskArgs) error { return nil }
  1088  		default:
  1089  			err = fmt.Errorf("don't know how to advance from %s to %s", state, nextState)
  1090  			state = vcopierCopyTaskFail
  1091  			goto END
  1092  		}
  1093  
  1094  		// tryAdvance tries to execute advanceFn, and also executes any
  1095  		// lifecycle hooks surrounding the provided state.
  1096  		//
  1097  		// If all lifecycle hooks and advanceFn are successfully executed,
  1098  		// tryAdvance will return state, which should be == the nextState that
  1099  		// we provided.
  1100  		//
  1101  		// If there was a failure executing lifecycle hooks or advanceFn,
  1102  		// tryAdvance will return a failure state (i.e. canceled or failed),
  1103  		// along with a diagnostic error.
  1104  		if state, err = task.lifecycle.tryAdvance(ctx, task.args, nextState, advanceFn); err != nil {
  1105  			goto END
  1106  		}
  1107  	}
  1108  
  1109  END:
  1110  	// At this point, we're in a "done" state (= canceled, completed, failed).
  1111  	// Notify any onResult callbacks.
  1112  	result := newVCopierCopyTaskResult(task.args, startedAt, state, err)
  1113  	task.lifecycle.onResult().notify(ctx, result)
  1114  
  1115  	return result
  1116  }
  1117  
  1118  func (vbc *vcopierCopyWorker) insertCopyState(ctx context.Context, lastpk *querypb.Row) error {
  1119  	var buf []byte
  1120  	buf, err := prototext.Marshal(&querypb.QueryResult{
  1121  		Fields: vbc.pkfields,
  1122  		Rows:   []*querypb.Row{lastpk},
  1123  	})
  1124  	if err != nil {
  1125  		return err
  1126  	}
  1127  	bv := map[string]*querypb.BindVariable{
  1128  		"lastpk": {
  1129  			Type:  sqltypes.VarBinary,
  1130  			Value: buf,
  1131  		},
  1132  	}
  1133  	copyStateInsert, err := vbc.copyStateInsert.GenerateQuery(bv, nil)
  1134  	if err != nil {
  1135  		return err
  1136  	}
  1137  	if _, err := vbc.vdbClient.Execute(copyStateInsert); err != nil {
  1138  		return err
  1139  	}
  1140  	return nil
  1141  }
  1142  
  1143  func (vbc *vcopierCopyWorker) insertRows(ctx context.Context, rows []*querypb.Row) (*sqltypes.Result, error) {
  1144  	return vbc.tablePlan.applyBulkInsert(
  1145  		&vbc.sqlbuffer,
  1146  		rows,
  1147  		func(sql string) (*sqltypes.Result, error) {
  1148  			return vbc.vdbClient.ExecuteWithRetry(ctx, sql)
  1149  		},
  1150  	)
  1151  }
  1152  
  1153  // open the vcopierCopyWorker. The provided arguments are used to generate
  1154  // statements for inserting rows and copy state.
  1155  func (vbc *vcopierCopyWorker) open(
  1156  	copyStateInsert *sqlparser.ParsedQuery,
  1157  	pkfields []*querypb.Field,
  1158  	tablePlan *TablePlan,
  1159  ) {
  1160  	if vbc.isOpen {
  1161  		return
  1162  	}
  1163  	vbc.copyStateInsert = copyStateInsert
  1164  	vbc.isOpen = true
  1165  	vbc.pkfields = pkfields
  1166  	vbc.tablePlan = tablePlan
  1167  }
  1168  
  1169  // vcopierCopyTaskStateIsDone returns true if the provided state is in one of
  1170  // these three states:
  1171  //
  1172  // * vcopierCopyTaskCancel
  1173  // * vcopierCopyTaskComplete
  1174  // * vcopierCopyTaskFail
  1175  func vcopierCopyTaskStateIsDone(vts vcopierCopyTaskState) bool {
  1176  	return vts == vcopierCopyTaskCancel ||
  1177  		vts == vcopierCopyTaskComplete ||
  1178  		vts == vcopierCopyTaskFail
  1179  }
  1180  
  1181  // vcopierCopyTaskGetNextState returns the optimistic next state. The next
  1182  // state after vcopierCopyTaskPending is vcopierCopyTaskInProgress, followed
  1183  // by vcopierCopyTaskInsertRows, etc.
  1184  func vcopierCopyTaskGetNextState(vts vcopierCopyTaskState) vcopierCopyTaskState {
  1185  	switch vts {
  1186  	case vcopierCopyTaskPending:
  1187  		return vcopierCopyTaskBegin
  1188  	case vcopierCopyTaskBegin:
  1189  		return vcopierCopyTaskInsertRows
  1190  	case vcopierCopyTaskInsertRows:
  1191  		return vcopierCopyTaskInsertCopyState
  1192  	case vcopierCopyTaskInsertCopyState:
  1193  		return vcopierCopyTaskCommit
  1194  	case vcopierCopyTaskCommit:
  1195  		return vcopierCopyTaskComplete
  1196  	}
  1197  	return vts
  1198  }