github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/errorutil/util.go (about)

     1  // Copyright 2021 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 errorutil
    15  
    16  import (
    17  	"strings"
    18  
    19  	gmysql "github.com/go-sql-driver/mysql"
    20  	"github.com/pingcap/errors"
    21  	"github.com/pingcap/tidb/pkg/infoschema"
    22  	"github.com/pingcap/tidb/pkg/parser/mysql"
    23  	"github.com/pingcap/tidb/pkg/util/dbterror"
    24  	"github.com/pingcap/tidb/pkg/util/dbutil"
    25  	dmretry "github.com/pingcap/tiflow/dm/pkg/retry"
    26  	frameModel "github.com/pingcap/tiflow/engine/framework/model"
    27  	cerror "github.com/pingcap/tiflow/pkg/errors"
    28  	v3rpc "go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
    29  )
    30  
    31  // IsIgnorableMySQLDDLError is used to check what error can be ignored
    32  // we can get error code from:
    33  // infoschema's error definition: https://github.com/pingcap/tidb/blob/master/infoschema/infoschema.go
    34  // DDL's error definition: https://github.com/pingcap/tidb/blob/master/ddl/ddl.go
    35  // tidb/mysql error code definition: https://github.com/pingcap/tidb/blob/master/mysql/errcode.go
    36  func IsIgnorableMySQLDDLError(err error) bool {
    37  	err = errors.Cause(err)
    38  	mysqlErr, ok := err.(*gmysql.MySQLError)
    39  	if !ok {
    40  		return false
    41  	}
    42  
    43  	errCode := errors.ErrCode(mysqlErr.Number)
    44  	switch errCode {
    45  	case infoschema.ErrDatabaseExists.Code(), infoschema.ErrDatabaseDropExists.Code(),
    46  		infoschema.ErrTableExists.Code(), infoschema.ErrTableDropExists.Code(),
    47  		infoschema.ErrColumnExists.Code(), infoschema.ErrIndexExists.Code(),
    48  		infoschema.ErrKeyNotExists.Code(), dbterror.ErrCantDropFieldOrKey.Code(),
    49  		infoschema.ErrColumnNotExists.Code(),
    50  		mysql.ErrDupKeyName, mysql.ErrSameNamePartition,
    51  		mysql.ErrDropPartitionNonExistent, mysql.ErrMultiplePriKey:
    52  		return true
    53  	default:
    54  		return false
    55  	}
    56  }
    57  
    58  // IsRetryableEtcdError is used to check what error can be retried.
    59  func IsRetryableEtcdError(err error) bool {
    60  	if err == nil {
    61  		return false
    62  	}
    63  	etcdErr := errors.Cause(err)
    64  
    65  	switch etcdErr {
    66  	// Etcd ResourceExhausted errors, may recover after some time
    67  	case v3rpc.ErrNoSpace, v3rpc.ErrTooManyRequests:
    68  		return true
    69  	// Etcd Unavailable errors, may be available after some time
    70  	// https://github.com/etcd-io/etcd/pull/9934/files#diff-6d8785d0c9eaf96bc3e2b29c36493c04R162-R167
    71  	// ErrStopped:
    72  	// one of the etcd nodes stopped from failure injection
    73  	// ErrNotCapable:
    74  	// capability check has not been done (in the beginning)
    75  	case v3rpc.ErrNoLeader, v3rpc.ErrLeaderChanged, v3rpc.ErrNotCapable, v3rpc.ErrStopped, v3rpc.ErrTimeout,
    76  		v3rpc.ErrTimeoutDueToLeaderFail, v3rpc.ErrGRPCTimeoutDueToConnectionLost, v3rpc.ErrUnhealthy:
    77  		return true
    78  	default:
    79  	}
    80  	// when the PD instance was deleted from the PD cluster, it may meet different errors.
    81  	// retry on such error make cdc robust to PD / ETCD cluster member removal.
    82  	// we should tolerant such case to make cdc robust to PD / ETCD cluster member change.
    83  	// see: https://github.com/etcd-io/etcd/blob/ae36a577d7be/raft/node.go#L35
    84  	if strings.Contains(etcdErr.Error(), "raft: stopped") {
    85  		return true
    86  	}
    87  	// see: https://github.com/pingcap/tiflow/issues/6720
    88  	if strings.Contains(etcdErr.Error(), "received prior goaway: code: NO_ERROR") {
    89  		return true
    90  	}
    91  
    92  	// this may happen if the PD instance shutdown by `kill -9`, no matter the instance is the leader or not.
    93  	if strings.Contains(etcdErr.Error(), "connection reset by peer") {
    94  		return true
    95  	}
    96  	return false
    97  }
    98  
    99  // IsRetryableDMLError check if the error is a retryable dml error.
   100  func IsRetryableDMLError(err error) bool {
   101  	if !cerror.IsRetryableError(err) {
   102  		return false
   103  	}
   104  	// Check if the error is connection errors that can retry safely.
   105  	if dmretry.IsConnectionError(err) {
   106  		return true
   107  	}
   108  	// Check if the error is a retriable TiDB error or MySQL error.
   109  	return dbutil.IsRetryableError(err)
   110  }
   111  
   112  // IsRetryableDDLError check if the error is a retryable ddl error.
   113  func IsRetryableDDLError(err error) bool {
   114  	if IsRetryableDMLError(err) {
   115  		return true
   116  	}
   117  
   118  	// All DDLs should be idempotent in theory.
   119  	if dmretry.IsUnretryableConnectionError(err) {
   120  		return true
   121  	}
   122  
   123  	err = errors.Cause(err)
   124  	mysqlErr, ok := err.(*gmysql.MySQLError)
   125  	if !ok {
   126  		return false
   127  	}
   128  
   129  	// If the error is in the black list, return false.
   130  	switch mysqlErr.Number {
   131  	case mysql.ErrAccessDenied,
   132  		mysql.ErrDBaccessDenied,
   133  		mysql.ErrSyntax,
   134  		mysql.ErrParse,
   135  		mysql.ErrNoDB,
   136  		mysql.ErrBadDB,
   137  		mysql.ErrNoSuchTable,
   138  		mysql.ErrNoSuchIndex,
   139  		mysql.ErrKeyColumnDoesNotExits,
   140  		mysql.ErrWrongColumnName,
   141  		mysql.ErrPartitionMgmtOnNonpartitioned,
   142  		mysql.ErrNonuniqTable:
   143  		return false
   144  	}
   145  	return true
   146  }
   147  
   148  // IsAccessDeniedError checks if the error is an access denied error.
   149  func IsAccessDeniedError(err error) bool {
   150  	err = errors.Cause(err)
   151  	mysqlErr, ok := err.(*gmysql.MySQLError)
   152  	if !ok {
   153  		return false
   154  	}
   155  	return mysqlErr.Number == mysql.ErrAccessDenied ||
   156  		mysqlErr.Number == mysql.ErrAccessDeniedNoPassword
   157  }
   158  
   159  // IsSyncPointIgnoreError returns whether the error is ignorable for syncpoint.
   160  func IsSyncPointIgnoreError(err error) bool {
   161  	err = errors.Cause(err)
   162  	mysqlErr, ok := err.(*gmysql.MySQLError)
   163  	if !ok {
   164  		return false
   165  	}
   166  	// We should ignore the error when the downstream has no
   167  	// such system variable for compatibility.
   168  	return mysqlErr.Number == mysql.ErrUnknownSystemVariable
   169  }
   170  
   171  // ConvertErr converts an error to a specific error by worker type.
   172  func ConvertErr(tp frameModel.WorkerType, err error) error {
   173  	switch tp {
   174  	case frameModel.DMJobMaster:
   175  		err = cerror.ToDMError(err)
   176  	default:
   177  	}
   178  	return err
   179  }