github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/errors/helper.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 errors
    15  
    16  import (
    17  	"context"
    18  	"net/http"
    19  	"strings"
    20  
    21  	"github.com/pingcap/errors"
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/grpc/status"
    24  )
    25  
    26  // WrapError generates a new error based on given `*errors.Error`, wraps the err
    27  // as cause error.
    28  // If given `err` is nil, returns a nil error, which a the different behavior
    29  // against `Wrap` function in pingcap/errors.
    30  func WrapError(rfcError *errors.Error, err error, args ...interface{}) error {
    31  	if err == nil {
    32  		return nil
    33  	}
    34  	return rfcError.Wrap(err).GenWithStackByArgs(args...)
    35  }
    36  
    37  // ChangeFeedGCFastFailError is read only.
    38  // If this type of error occurs in a changefeed, it means that the data it
    39  // wants to replicate has been or will be GC. So it makes no sense to try to
    40  // resume the changefeed, and the changefeed should immediately be failed.
    41  var ChangeFeedGCFastFailError = []*errors.Error{
    42  	ErrGCTTLExceeded, ErrSnapshotLostByGC, ErrStartTsBeforeGC,
    43  }
    44  
    45  // IsChangefeedGCFastFailError checks if an error is a ChangefeedFastFailError
    46  func IsChangefeedGCFastFailError(err error) bool {
    47  	if err == nil {
    48  		return false
    49  	}
    50  	for _, e := range ChangeFeedGCFastFailError {
    51  		if e.Equal(err) {
    52  			return true
    53  		}
    54  		rfcCode, ok := RFCCode(err)
    55  		if ok && e.RFCCode() == rfcCode {
    56  			return true
    57  		}
    58  	}
    59  	return false
    60  }
    61  
    62  // IsChangefeedGCFastFailErrorCode checks the error code, returns true if it is a
    63  // ChangefeedFastFailError code
    64  func IsChangefeedGCFastFailErrorCode(errCode errors.RFCErrorCode) bool {
    65  	for _, e := range ChangeFeedGCFastFailError {
    66  		if errCode == e.RFCCode() {
    67  			return true
    68  		}
    69  	}
    70  	return false
    71  }
    72  
    73  var changefeedUnRetryableErrors = []*errors.Error{
    74  	ErrExpressionColumnNotFound,
    75  	ErrExpressionParseFailed,
    76  	ErrSchemaSnapshotNotFound,
    77  	ErrSyncRenameTableFailed,
    78  	ErrChangefeedUnretryable,
    79  	ErrCorruptedDataMutation,
    80  	ErrDispatcherFailed,
    81  	ErrColumnSelectorFailed,
    82  
    83  	ErrSinkURIInvalid,
    84  	ErrKafkaInvalidConfig,
    85  	ErrMySQLInvalidConfig,
    86  	ErrStorageSinkInvalidConfig,
    87  }
    88  
    89  // ShouldFailChangefeed returns true if an error is a changefeed not retry error.
    90  func ShouldFailChangefeed(err error) bool {
    91  	for _, e := range changefeedUnRetryableErrors {
    92  		if e.Equal(err) {
    93  			return true
    94  		}
    95  		if code, ok := RFCCode(err); ok {
    96  			if code == e.RFCCode() {
    97  				return true
    98  			}
    99  		}
   100  		if strings.Contains(err.Error(), string(e.RFCCode())) {
   101  			return true
   102  		}
   103  	}
   104  	return false
   105  }
   106  
   107  // RFCCode returns a RFCCode from an error
   108  func RFCCode(err error) (errors.RFCErrorCode, bool) {
   109  	type rfcCoder interface {
   110  		RFCCode() errors.RFCErrorCode
   111  	}
   112  	if terr, ok := err.(rfcCoder); ok {
   113  		return terr.RFCCode(), true
   114  	}
   115  	cause := errors.Unwrap(err)
   116  	if cause == nil {
   117  		return "", false
   118  	}
   119  	if terr, ok := cause.(rfcCoder); ok {
   120  		return terr.RFCCode(), true
   121  	}
   122  	return RFCCode(cause)
   123  }
   124  
   125  // IsDupEntryError checks if an error is a duplicate entry error.
   126  func IsDupEntryError(err error) bool {
   127  	if err == nil {
   128  		return false
   129  	}
   130  	if ErrMySQLDuplicateEntry.Equal(err) {
   131  		return true
   132  	}
   133  	if code, ok := RFCCode(err); ok {
   134  		if code == ErrMySQLDuplicateEntry.RFCCode() {
   135  			return true
   136  		}
   137  	}
   138  	if strings.Contains(err.Error(), string(ErrMySQLDuplicateEntry.RFCCode())) {
   139  		return true
   140  	}
   141  	return false
   142  }
   143  
   144  // IsRetryableError check the error is safe or worth to retry
   145  func IsRetryableError(err error) bool {
   146  	if err == nil {
   147  		return false
   148  	}
   149  
   150  	switch errors.Cause(err) {
   151  	case context.Canceled, context.DeadlineExceeded:
   152  		return false
   153  	}
   154  	return true
   155  }
   156  
   157  var cliUnprintableError = []*errors.Error{ErrCliAborted}
   158  
   159  // IsCliUnprintableError returns true if the error should not be printed in cli.
   160  func IsCliUnprintableError(err error) bool {
   161  	if err == nil {
   162  		return false
   163  	}
   164  	for _, e := range cliUnprintableError {
   165  		if strings.Contains(err.Error(), string(e.RFCCode())) {
   166  			return true
   167  		}
   168  	}
   169  	return false
   170  }
   171  
   172  // WrapChangefeedUnretryableErr wraps an error into ErrChangefeedUnRetryable.
   173  func WrapChangefeedUnretryableErr(err error, args ...interface{}) error {
   174  	return WrapError(ErrChangefeedUnretryable, err, args...)
   175  }
   176  
   177  // IsContextCanceledError checks if an error is caused by context.Canceled.
   178  func IsContextCanceledError(err error) bool {
   179  	return errors.Cause(err) == context.Canceled
   180  }
   181  
   182  // IsContextDeadlineExceededError checks if an error is caused by context.DeadlineExceeded.
   183  func IsContextDeadlineExceededError(err error) bool {
   184  	return errors.Cause(err) == context.DeadlineExceeded
   185  }
   186  
   187  // httpStatusCodeMapping is a mapping from RFC error code to HTTP status code.
   188  // It does not contain all RFC error codes, only the ones what we think
   189  // are not just internal errors.
   190  var httpStatusCodeMapping = map[errors.RFCErrorCode]int{
   191  	ErrUnknown.RFCCode():               http.StatusInternalServerError,
   192  	ErrInvalidArgument.RFCCode():       http.StatusBadRequest,
   193  	ErrMasterNotReady.RFCCode():        http.StatusServiceUnavailable,
   194  	ErrJobNotFound.RFCCode():           http.StatusNotFound,
   195  	ErrJobAlreadyExists.RFCCode():      http.StatusConflict,
   196  	ErrJobAlreadyCanceled.RFCCode():    http.StatusBadRequest,
   197  	ErrJobNotTerminated.RFCCode():      http.StatusBadRequest,
   198  	ErrJobNotRunning.RFCCode():         http.StatusBadRequest,
   199  	ErrMetaStoreNotExists.RFCCode():    http.StatusNotFound,
   200  	ErrResourceAlreadyExists.RFCCode(): http.StatusConflict,
   201  	ErrIllegalResourcePath.RFCCode():   http.StatusBadRequest,
   202  	ErrResourceDoesNotExist.RFCCode():  http.StatusNotFound,
   203  	ErrResourceConflict.RFCCode():      http.StatusConflict,
   204  }
   205  
   206  // HTTPStatusCode returns the HTTP status code for the given error.
   207  func HTTPStatusCode(err error) int {
   208  	if err == nil {
   209  		return http.StatusOK
   210  	}
   211  	rfcCode, ok := RFCCode(err)
   212  	if !ok {
   213  		if IsContextCanceledError(err) {
   214  			return 499 // Client Closed Request
   215  		}
   216  		if IsContextDeadlineExceededError(err) {
   217  			return http.StatusGatewayTimeout
   218  		}
   219  		return http.StatusInternalServerError
   220  	}
   221  	if code, ok := httpStatusCodeMapping[rfcCode]; ok {
   222  		return code
   223  	}
   224  	return http.StatusInternalServerError
   225  }
   226  
   227  // gRPCStatusCodeMapping is a mapping from RFC error code to gRPC status code.
   228  // It does not contain all RFC error codes, only the ones what we think
   229  // are not just internal errors.
   230  var gRPCStatusCodeMapping = map[errors.RFCErrorCode]codes.Code{
   231  	ErrUnknown.RFCCode():               codes.Unknown,
   232  	ErrInvalidArgument.RFCCode():       codes.InvalidArgument,
   233  	ErrMasterNotReady.RFCCode():        codes.Unavailable,
   234  	ErrUnknownExecutor.RFCCode():       codes.InvalidArgument,
   235  	ErrTombstoneExecutor.RFCCode():     codes.FailedPrecondition,
   236  	ErrJobNotFound.RFCCode():           codes.NotFound,
   237  	ErrJobAlreadyExists.RFCCode():      codes.AlreadyExists,
   238  	ErrJobAlreadyCanceled.RFCCode():    codes.FailedPrecondition,
   239  	ErrJobNotTerminated.RFCCode():      codes.FailedPrecondition,
   240  	ErrJobNotRunning.RFCCode():         codes.FailedPrecondition,
   241  	ErrMetaStoreNotExists.RFCCode():    codes.NotFound,
   242  	ErrResourceAlreadyExists.RFCCode(): codes.AlreadyExists,
   243  	ErrIllegalResourcePath.RFCCode():   codes.InvalidArgument,
   244  	ErrResourceDoesNotExist.RFCCode():  codes.NotFound,
   245  	ErrResourceConflict.RFCCode():      codes.FailedPrecondition,
   246  }
   247  
   248  // GRPCStatusCode returns the gRPC status code for the given error.
   249  func GRPCStatusCode(err error) codes.Code {
   250  	if err == nil {
   251  		return codes.OK
   252  	}
   253  	if s, ok := status.FromError(err); ok {
   254  		return s.Code()
   255  	}
   256  	rfcCode, ok := RFCCode(err)
   257  	if !ok {
   258  		if IsContextCanceledError(err) {
   259  			return codes.Canceled
   260  		}
   261  		if IsContextDeadlineExceededError(err) {
   262  			return codes.DeadlineExceeded
   263  		}
   264  		return codes.Unknown
   265  	}
   266  	if code, ok := gRPCStatusCodeMapping[rfcCode]; ok {
   267  		return code
   268  	}
   269  	return codes.Internal
   270  }