github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/sql/colexecerror/error.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package colexecerror
    12  
    13  import (
    14  	"bufio"
    15  	"context"
    16  	"runtime/debug"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgcode"
    20  	"github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgerror"
    21  	"github.com/cockroachdb/errors"
    22  	"github.com/gogo/protobuf/proto"
    23  )
    24  
    25  const panicLineSubstring = "runtime/panic.go"
    26  
    27  // CatchVectorizedRuntimeError executes operation, catches a runtime error if
    28  // it is coming from the vectorized engine, and returns it. If an error not
    29  // related to the vectorized engine occurs, it is not recovered from.
    30  func CatchVectorizedRuntimeError(operation func()) (retErr error) {
    31  	defer func() {
    32  		panicObj := recover()
    33  		if panicObj == nil {
    34  			// No panic happened, so the operation must have been executed
    35  			// successfully.
    36  			return
    37  		}
    38  
    39  		// Find where the panic came from and only proceed if it is related to the
    40  		// vectorized engine.
    41  		stackTrace := string(debug.Stack())
    42  		scanner := bufio.NewScanner(strings.NewReader(stackTrace))
    43  		panicLineFound := false
    44  		for scanner.Scan() {
    45  			if strings.Contains(scanner.Text(), panicLineSubstring) {
    46  				panicLineFound = true
    47  				break
    48  			}
    49  		}
    50  		if !panicLineFound {
    51  			panic(errors.AssertionFailedf("panic line %q not found in the stack trace\n%s", panicLineSubstring, stackTrace))
    52  		}
    53  		if !scanner.Scan() {
    54  			panic(errors.AssertionFailedf("unexpectedly there is no line below the panic line in the stack trace\n%s", stackTrace))
    55  		}
    56  		panicEmittedFrom := strings.TrimSpace(scanner.Text())
    57  		if !shouldCatchPanic(panicEmittedFrom) {
    58  			panic(panicObj)
    59  		}
    60  
    61  		err, ok := panicObj.(error)
    62  		if !ok {
    63  			// Not an error object. Definitely unexpected.
    64  			retErr = errors.AssertionFailedf("unexpected error from the vectorized runtime: %+v", panicObj)
    65  			return
    66  		}
    67  		retErr = err
    68  
    69  		if _, ok := panicObj.(*StorageError); ok {
    70  			// A StorageError was caused by something below SQL, and represents
    71  			// an error that we'd simply like to propagate along.
    72  			// Do nothing.
    73  			return
    74  		}
    75  
    76  		annotateErrorWithoutCode := true
    77  		var nie *notInternalError
    78  		if errors.Is(err, context.Canceled) || errors.As(err, &nie) {
    79  			// We don't want to annotate the context cancellation and
    80  			// notInternalError errors in case they don't have a valid PG code
    81  			// so that the sentry report is not sent (errors with failed
    82  			// assertions get sentry reports).
    83  			annotateErrorWithoutCode = false
    84  		}
    85  		if code := pgerror.GetPGCode(err); annotateErrorWithoutCode && code == pgcode.Uncategorized {
    86  			// Any error without a code already is "surprising" and
    87  			// needs to be annotated to indicate that it was
    88  			// unexpected.
    89  			retErr = errors.NewAssertionErrorWithWrappedErrf(err, "unexpected error from the vectorized engine")
    90  		}
    91  	}()
    92  	operation()
    93  	return retErr
    94  }
    95  
    96  // We use the approach of allow-listing the packages the panics from which are
    97  // safe to catch (which is the case when the code doesn't update shared state
    98  // and doesn't manipulate locks).
    99  //
   100  // Multiple actual packages can have the same prefix as a single constant string
   101  // defined below, but all of such packages are allowed to be caught from.
   102  const (
   103  	colPackagesPrefix      = "github.com/cockroachdb/cockroachdb-parser/pkg/col"
   104  	encodingPackagePrefix  = "github.com/cockroachdb/cockroachdb-parser/pkg/util/encoding"
   105  	execinfraPackagePrefix = "github.com/cockroachdb/cockroachdb-parser/pkg/sql/execinfra"
   106  	sqlColPackagesPrefix   = "github.com/cockroachdb/cockroachdb-parser/pkg/sql/col"
   107  	sqlRowPackagesPrefix   = "github.com/cockroachdb/cockroachdb-parser/pkg/sql/row"
   108  	sqlSemPackagesPrefix   = "github.com/cockroachdb/cockroachdb-parser/pkg/sql/sem"
   109  )
   110  
   111  // shouldCatchPanic checks whether the panic that was emitted from
   112  // panicEmittedFrom line of code (which contains the full path to the function
   113  // where the panic originated) should be caught by the vectorized engine.
   114  //
   115  // The vectorized engine uses the panic-catch mechanism of error propagation, so
   116  // we need to catch all of its errors. We also want to catch any panics that
   117  // occur because of internal errors in some execution component (e.g. builtins).
   118  //
   119  // panicEmittedFrom must be trimmed to not have any white spaces in the prefix.
   120  func shouldCatchPanic(panicEmittedFrom string) bool {
   121  	const panicFromTheCatcherItselfPrefix = "github.com/cockroachdb/cockroachdb-parser/pkg/sql/colexecerror.CatchVectorizedRuntimeError"
   122  	if strings.HasPrefix(panicEmittedFrom, panicFromTheCatcherItselfPrefix) {
   123  		// This panic came from the catcher itself, so we will propagate it
   124  		// unchanged by the higher-level catchers.
   125  		return false
   126  	}
   127  	const nonCatchablePanicPrefix = "github.com/cockroachdb/cockroachdb-parser/pkg/sql/colexecerror.NonCatchablePanic"
   128  	if strings.HasPrefix(panicEmittedFrom, nonCatchablePanicPrefix) {
   129  		// This panic came from NonCatchablePanic() method and should not be
   130  		// caught.
   131  		return false
   132  	}
   133  	return strings.HasPrefix(panicEmittedFrom, colPackagesPrefix) ||
   134  		strings.HasPrefix(panicEmittedFrom, encodingPackagePrefix) ||
   135  		strings.HasPrefix(panicEmittedFrom, execinfraPackagePrefix) ||
   136  		strings.HasPrefix(panicEmittedFrom, sqlColPackagesPrefix) ||
   137  		strings.HasPrefix(panicEmittedFrom, sqlRowPackagesPrefix) ||
   138  		strings.HasPrefix(panicEmittedFrom, sqlSemPackagesPrefix)
   139  }
   140  
   141  // StorageError is an error that was created by a component below the sql
   142  // stack, such as the network or storage layers. A StorageError will be bubbled
   143  // up all the way past the SQL layer unchanged.
   144  type StorageError struct {
   145  	error
   146  }
   147  
   148  // Cause implements the Causer interface.
   149  func (s *StorageError) Cause() error {
   150  	return s.error
   151  }
   152  
   153  // NewStorageError returns a new storage error. This can be used to propagate
   154  // an error through the exec subsystem unchanged.
   155  func NewStorageError(err error) *StorageError {
   156  	return &StorageError{error: err}
   157  }
   158  
   159  // notInternalError is an error that occurs not because the vectorized engine
   160  // happens to be in an unexpected state (for example, it was caused by a
   161  // non-columnar builtin).
   162  // notInternalError will be returned to the client not as an
   163  // "internal error" and without the stack trace.
   164  type notInternalError struct {
   165  	cause error
   166  }
   167  
   168  func newNotInternalError(err error) *notInternalError {
   169  	return &notInternalError{cause: err}
   170  }
   171  
   172  var (
   173  	_ errors.Wrapper = &notInternalError{}
   174  )
   175  
   176  func (e *notInternalError) Error() string { return e.cause.Error() }
   177  func (e *notInternalError) Cause() error  { return e.cause }
   178  func (e *notInternalError) Unwrap() error { return e.Cause() }
   179  
   180  func decodeNotInternalError(
   181  	_ context.Context, cause error, _ string, _ []string, _ proto.Message,
   182  ) error {
   183  	return newNotInternalError(cause)
   184  }
   185  
   186  func init() {
   187  	errors.RegisterWrapperDecoder(errors.GetTypeKey((*notInternalError)(nil)), decodeNotInternalError)
   188  }
   189  
   190  // InternalError simply panics with the provided object. It will always be
   191  // caught and returned as internal error to the client with the corresponding
   192  // stack trace. This method should be called to propagate errors that resulted
   193  // in the vectorized engine being in an *unexpected* state.
   194  func InternalError(err error) {
   195  	panic(err)
   196  }
   197  
   198  // ExpectedError panics with the error that is wrapped by
   199  // notInternalError which will not be treated as internal error and
   200  // will not have a printed out stack trace. This method should be called to
   201  // propagate errors that the vectorized engine *expects* to occur.
   202  func ExpectedError(err error) {
   203  	panic(newNotInternalError(err))
   204  }
   205  
   206  // NonCatchablePanic is the equivalent of Golang's 'panic' word that can be used
   207  // in order to crash the goroutine. It could be used by the testing code within
   208  // the vectorized engine to simulate a panic that occurs outside of the engine
   209  // (and, thus, should not be caught).
   210  func NonCatchablePanic(object interface{}) {
   211  	panic(object)
   212  }