github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/changefeedccl/errors.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package changefeedccl
    10  
    11  import (
    12  	"fmt"
    13  	"reflect"
    14  	"strings"
    15  
    16  	"github.com/cockroachdb/errors"
    17  )
    18  
    19  const retryableErrorString = "retryable changefeed error"
    20  
    21  type retryableError struct {
    22  	wrapped error
    23  }
    24  
    25  // MarkRetryableError wraps the given error, marking it as retryable to
    26  // changefeeds.
    27  func MarkRetryableError(e error) error {
    28  	return &retryableError{wrapped: e}
    29  }
    30  
    31  // Error implements the error interface.
    32  func (e *retryableError) Error() string {
    33  	return fmt.Sprintf("%s: %s", retryableErrorString, e.wrapped.Error())
    34  }
    35  
    36  // Cause implements the github.com/pkg/errors.causer interface.
    37  func (e *retryableError) Cause() error { return e.wrapped }
    38  
    39  // Unwrap implements the github.com/golang/xerrors.Wrapper interface, which is
    40  // planned to be moved to the stdlib in go 1.13.
    41  func (e *retryableError) Unwrap() error { return e.wrapped }
    42  
    43  // IsRetryableError returns true if the supplied error, or any of its parent
    44  // causes, is a IsRetryableError.
    45  func IsRetryableError(err error) bool {
    46  	if err == nil {
    47  		return false
    48  	}
    49  	if errors.HasType(err, (*retryableError)(nil)) {
    50  		return true
    51  	}
    52  
    53  	// TODO(knz): this is a bad implementation. Make it go away
    54  	// by avoiding string comparisons.
    55  
    56  	errStr := err.Error()
    57  	if strings.Contains(errStr, retryableErrorString) {
    58  		// If a RetryableError occurs on a remote node, DistSQL serializes it such
    59  		// that we can't recover the structure and we have to rely on this
    60  		// unfortunate string comparison.
    61  		return true
    62  	}
    63  	if strings.Contains(errStr, `rpc error`) {
    64  		// When a crdb node dies, any DistSQL flows with processors scheduled on
    65  		// it get an error with "rpc error" in the message from the call to
    66  		// `(*DistSQLPlanner).Run`.
    67  		return true
    68  	}
    69  	return false
    70  }
    71  
    72  // MaybeStripRetryableErrorMarker performs some minimal attempt to clean the
    73  // RetryableError marker out. This won't do anything if the RetryableError
    74  // itself has been wrapped, but that's okay, we'll just have an uglier string.
    75  func MaybeStripRetryableErrorMarker(err error) error {
    76  	// The following is a hack to work around the error cast linter.
    77  	// What we're doing here is really not kosher; this function
    78  	// has no business in assuming that the retryableError{} wrapper
    79  	// has not been wrapped already. We could even expect that
    80  	// it gets wrapped in the common case.
    81  	// TODO(knz): Remove/replace this.
    82  	if reflect.TypeOf(err) == retryableErrorType {
    83  		err = errors.UnwrapOnce(err)
    84  	}
    85  	return err
    86  }
    87  
    88  var retryableErrorType = reflect.TypeOf((*retryableError)(nil))