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 ¬InternalError{cause: err} 170 } 171 172 var ( 173 _ errors.Wrapper = ¬InternalError{} 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 }