github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/colexecbase/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 "fmt" 17 "runtime/debug" 18 "strings" 19 20 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 21 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 22 "github.com/cockroachdb/errors" 23 "github.com/gogo/protobuf/proto" 24 ) 25 26 const panicLineSubstring = "runtime/panic.go" 27 28 // CatchVectorizedRuntimeError executes operation, catches a runtime error if 29 // it is coming from the vectorized engine, and returns it. If an error not 30 // related to the vectorized engine occurs, it is not recovered from. 31 func CatchVectorizedRuntimeError(operation func()) (retErr error) { 32 defer func() { 33 panicObj := recover() 34 if panicObj == nil { 35 // No panic happened, so the operation must have been executed 36 // successfully. 37 return 38 } 39 40 // Find where the panic came from and only proceed if it is related to the 41 // vectorized engine. 42 stackTrace := string(debug.Stack()) 43 scanner := bufio.NewScanner(strings.NewReader(stackTrace)) 44 panicLineFound := false 45 for scanner.Scan() { 46 if strings.Contains(scanner.Text(), panicLineSubstring) { 47 panicLineFound = true 48 break 49 } 50 } 51 if !panicLineFound { 52 panic(fmt.Sprintf("panic line %q not found in the stack trace\n%s", panicLineSubstring, stackTrace)) 53 } 54 if !scanner.Scan() { 55 panic(fmt.Sprintf("unexpectedly there is no line below the panic line in the stack trace\n%s", stackTrace)) 56 } 57 panicEmittedFrom := strings.TrimSpace(scanner.Text()) 58 if !isPanicFromVectorizedEngine(panicEmittedFrom) { 59 // Do not recover from the panic not related to the vectorized 60 // engine. 61 panic(panicObj) 62 } 63 64 err, ok := panicObj.(error) 65 if !ok { 66 // Not an error object. Definitely unexpected. 67 retErr = errors.AssertionFailedf("unexpected error from the vectorized runtime: %+v", panicObj) 68 return 69 } 70 retErr = err 71 72 if _, ok := panicObj.(*StorageError); ok { 73 // A StorageError was caused by something below SQL, and represents 74 // an error that we'd simply like to propagate along. 75 // Do nothing. 76 return 77 } 78 79 annotateErrorWithoutCode := true 80 var nie *notInternalError 81 if errors.As(err, &nie) { 82 // A notInternalError was not caused by the vectorized engine and 83 // represents an error that we don't want to annotate in case it 84 // doesn't have a valid PG code. 85 annotateErrorWithoutCode = false 86 } 87 if code := pgerror.GetPGCode(err); annotateErrorWithoutCode && code == pgcode.Uncategorized { 88 // Any error without a code already is "surprising" and 89 // needs to be annotated to indicate that it was 90 // unexpected. 91 retErr = errors.NewAssertionErrorWithWrappedErrf(err, "unexpected error from the vectorized engine") 92 } 93 }() 94 operation() 95 return retErr 96 } 97 98 const ( 99 colPackagesPrefix = "github.com/cockroachdb/cockroach/pkg/col" 100 execinfraPackagePrefix = "github.com/cockroachdb/cockroach/pkg/sql/execinfra" 101 rowexecPackagePrefix = "github.com/cockroachdb/cockroach/pkg/sql/rowexec" 102 sqlColPackagesPrefix = "github.com/cockroachdb/cockroach/pkg/sql/col" 103 treePackagePrefix = "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 104 ) 105 106 // isPanicFromVectorizedEngine checks whether the panic that was emitted from 107 // panicEmittedFrom line of code (which includes package name as well as the 108 // file name and the line number) came from the vectorized engine. 109 // panicEmittedFrom must be trimmed to not have any white spaces in the prefix. 110 func isPanicFromVectorizedEngine(panicEmittedFrom string) bool { 111 const testExceptionPrefix = "github.com/cockroachdb/cockroach/pkg/sql/colflow_test.(*testNonVectorizedPanicEmitter)" 112 if strings.HasPrefix(panicEmittedFrom, testExceptionPrefix) { 113 // Although the panic appears to be coming from the vectorized engine, it 114 // is intended to not be caught in order to test the panic propagation, so 115 // we say that the panic is not from the vectorized engine. 116 return false 117 } 118 return strings.HasPrefix(panicEmittedFrom, colPackagesPrefix) || 119 strings.HasPrefix(panicEmittedFrom, execinfraPackagePrefix) || 120 strings.HasPrefix(panicEmittedFrom, rowexecPackagePrefix) || 121 strings.HasPrefix(panicEmittedFrom, sqlColPackagesPrefix) || 122 strings.HasPrefix(panicEmittedFrom, treePackagePrefix) 123 } 124 125 // StorageError is an error that was created by a component below the sql 126 // stack, such as the network or storage layers. A StorageError will be bubbled 127 // up all the way past the SQL layer unchanged. 128 type StorageError struct { 129 error 130 } 131 132 // Cause implements the Causer interface. 133 func (s *StorageError) Cause() error { 134 return s.error 135 } 136 137 // NewStorageError returns a new storage error. This can be used to propagate 138 // an error through the exec subsystem unchanged. 139 func NewStorageError(err error) *StorageError { 140 return &StorageError{error: err} 141 } 142 143 // notInternalError is an error that occurs not because the vectorized engine 144 // happens to be in an unexpected state (for example, it was caused by a 145 // non-columnar builtin). 146 // notInternalError will be returned to the client not as an 147 // "internal error" and without the stack trace. 148 type notInternalError struct { 149 cause error 150 } 151 152 func newNotInternalError(err error) *notInternalError { 153 return ¬InternalError{cause: err} 154 } 155 156 var ( 157 _ errors.Wrapper = ¬InternalError{} 158 ) 159 160 func (e *notInternalError) Error() string { return e.cause.Error() } 161 func (e *notInternalError) Cause() error { return e.cause } 162 func (e *notInternalError) Unwrap() error { return e.Cause() } 163 164 func decodeNotInternalError( 165 _ context.Context, cause error, _ string, _ []string, _ proto.Message, 166 ) error { 167 return newNotInternalError(cause) 168 } 169 170 func init() { 171 errors.RegisterWrapperDecoder(errors.GetTypeKey((*notInternalError)(nil)), decodeNotInternalError) 172 } 173 174 // InternalError simply panics with the provided object. It will always be 175 // caught and returned as internal error to the client with the corresponding 176 // stack trace. This method should be called to propagate errors that resulted 177 // in the vectorized engine being in an *unexpected* state. 178 func InternalError(err interface{}) { 179 panic(err) 180 } 181 182 // ExpectedError panics with the error that is wrapped by 183 // notInternalError which will not be treated as internal error and 184 // will not have a printed out stack trace. This method should be called to 185 // propagate errors that the vectorized engine *expects* to occur. 186 func ExpectedError(err error) { 187 panic(newNotInternalError(err)) 188 }