github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/syncer/shardddl/pessimist.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package shardddl
    15  
    16  import (
    17  	"context"
    18  	"sync"
    19  
    20  	tcontext "github.com/pingcap/tiflow/dm/pkg/context"
    21  	"github.com/pingcap/tiflow/dm/pkg/etcdutil"
    22  	"github.com/pingcap/tiflow/dm/pkg/log"
    23  	"github.com/pingcap/tiflow/dm/pkg/shardddl/pessimism"
    24  	"github.com/pingcap/tiflow/dm/pkg/terror"
    25  	clientv3 "go.etcd.io/etcd/client/v3"
    26  	"go.uber.org/zap"
    27  )
    28  
    29  // Pessimist used to coordinate the shard DDL migration in pessimism mode.
    30  type Pessimist struct {
    31  	mu sync.RWMutex
    32  
    33  	logger log.Logger
    34  	cli    *clientv3.Client
    35  	task   string
    36  	source string
    37  
    38  	// the shard DDL info which is pending to handle.
    39  	pendingInfo *pessimism.Info
    40  	// the shard DDL lock operation which is pending to handle.
    41  	pendingOp *pessimism.Operation
    42  }
    43  
    44  // NewPessimist creates a new Pessimist instance.
    45  func NewPessimist(pLogger *log.Logger, cli *clientv3.Client, task, source string) *Pessimist {
    46  	return &Pessimist{
    47  		logger: pLogger.WithFields(zap.String("component", "shard DDL pessimist")),
    48  		cli:    cli,
    49  		task:   task,
    50  		source: source,
    51  	}
    52  }
    53  
    54  // Reset resets the internal state of the pessimist.
    55  func (p *Pessimist) Reset() {
    56  	p.mu.Lock()
    57  	defer p.mu.Unlock()
    58  
    59  	p.pendingInfo = nil
    60  	p.pendingOp = nil
    61  }
    62  
    63  // ConstructInfo constructs a shard DDL info.
    64  func (p *Pessimist) ConstructInfo(schema, table string, ddls []string) pessimism.Info {
    65  	return pessimism.NewInfo(p.task, p.source, schema, table, ddls)
    66  }
    67  
    68  // PutInfo puts the shard DDL info into etcd and returns the revision.
    69  func (p *Pessimist) PutInfo(ctx context.Context, info pessimism.Info) (int64, error) {
    70  	// put info only no previous operation exists or not done.
    71  	rev, putted, err := pessimism.PutInfoIfOpNotDone(p.cli, info)
    72  	if err != nil {
    73  		return 0, err
    74  	} else if putted {
    75  		p.mu.Lock()
    76  		p.pendingInfo = &info
    77  		p.mu.Unlock()
    78  
    79  		return rev, nil
    80  	}
    81  
    82  	p.logger.Warn("the previous shard DDL operation still exists, waiting for it to be deleted", zap.Stringer("info", info))
    83  
    84  	// wait for the operation to be deleted.
    85  	ctx2, cancel2 := context.WithCancel(ctx)
    86  	defer cancel2()
    87  	ch := make(chan pessimism.Operation, 1)
    88  	errCh := make(chan error, 1)
    89  	go pessimism.WatchOperationDelete(ctx2, p.cli, info.Task, info.Source, rev, ch, errCh)
    90  
    91  	select {
    92  	case <-ch: // deleted.
    93  	case err = <-errCh:
    94  		return 0, err
    95  	case <-ctx.Done():
    96  		return 0, ctx.Err()
    97  	}
    98  
    99  	rev, err = pessimism.PutInfo(p.cli, info)
   100  	if err != nil {
   101  		return 0, err
   102  	}
   103  
   104  	p.mu.Lock()
   105  	p.pendingInfo = &info
   106  	p.mu.Unlock()
   107  
   108  	return rev, nil
   109  }
   110  
   111  // GetOperation gets the shard DDL lock operation relative to the shard DDL info.
   112  func (p *Pessimist) GetOperation(ctx context.Context, info pessimism.Info, rev int64) (pessimism.Operation, error) {
   113  	ctx2, cancel2 := context.WithCancel(ctx)
   114  	defer cancel2()
   115  
   116  	ch := make(chan pessimism.Operation, 1)
   117  	errCh := make(chan error, 1)
   118  	go pessimism.WatchOperationPut(ctx2, p.cli, info.Task, info.Source, rev, ch, errCh)
   119  
   120  	select {
   121  	case op := <-ch:
   122  		p.mu.Lock()
   123  		p.pendingOp = &op
   124  		p.mu.Unlock()
   125  		return op, nil
   126  	case err := <-errCh:
   127  		return pessimism.Operation{}, err
   128  	case <-ctx.Done():
   129  		return pessimism.Operation{}, ctx.Err()
   130  	}
   131  }
   132  
   133  // DoneOperationDeleteInfo marks the shard DDL lock operation as done and delete the shard DDL info.
   134  func (p *Pessimist) DoneOperationDeleteInfo(op pessimism.Operation, info pessimism.Info) error {
   135  	op.Done = true // mark the operation as `done`.
   136  
   137  	_, _, err := etcdutil.DoTxnWithRepeatable(p.cli, func(_ *tcontext.Context, cli *clientv3.Client) (interface{}, error) {
   138  		done, _, err := pessimism.PutOperationDeleteExistInfo(cli, op, info)
   139  		if err != nil {
   140  			return nil, err
   141  		} else if !done {
   142  			return nil, terror.ErrWorkerDDLLockInfoNotFound.Generatef("DDL info for (%s, %s) not found", info.Task, info.Source)
   143  		}
   144  		return nil, nil
   145  	})
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	p.mu.Lock()
   151  	p.pendingInfo = nil
   152  	p.pendingOp = nil
   153  	p.mu.Unlock()
   154  
   155  	return nil
   156  }
   157  
   158  // PendingInfo returns the shard DDL info which is pending to handle.
   159  func (p *Pessimist) PendingInfo() *pessimism.Info {
   160  	p.mu.RLock()
   161  	defer p.mu.RUnlock()
   162  
   163  	if p.pendingInfo == nil {
   164  		return nil
   165  	}
   166  	info := *p.pendingInfo
   167  	return &info
   168  }
   169  
   170  // PendingOperation returns the shard DDL lock operation which is pending to handle.
   171  func (p *Pessimist) PendingOperation() *pessimism.Operation {
   172  	p.mu.RLock()
   173  	defer p.mu.RUnlock()
   174  
   175  	if p.pendingOp == nil {
   176  		return nil
   177  	}
   178  	op := *p.pendingOp
   179  	return &op
   180  }