github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/timeutil/timeout_error.go (about) 1 // Copyright 2021 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 timeutil 12 13 import ( 14 "context" 15 "fmt" 16 "net" 17 "time" 18 19 "github.com/cockroachdb/errors" 20 "github.com/cockroachdb/errors/errorspb" 21 "github.com/gogo/protobuf/proto" 22 ) 23 24 // TimeoutError is a wrapped ContextDeadlineExceeded error. It indicates that 25 // an operation didn't complete within its designated timeout. 26 type TimeoutError struct { 27 // The operation that timed out. 28 operation string 29 // The configured timeout. 30 timeout time.Duration 31 // The duration of the operation. This is usually expected to be the same as 32 // the timeout, but can be longer if the timeout was not observed expediently 33 // (because the ctx was not checked sufficiently often). 34 took time.Duration 35 cause error 36 } 37 38 var _ error = (*TimeoutError)(nil) 39 var _ fmt.Formatter = (*TimeoutError)(nil) 40 var _ errors.SafeFormatter = (*TimeoutError)(nil) 41 42 // We implement net.Error the same way that context.DeadlineExceeded does, so 43 // that people looking for net.Error attributes will still find them. 44 var _ net.Error = (*TimeoutError)(nil) 45 46 // Operation returns the name of the operation that timed out. 47 func (t *TimeoutError) Operation() string { 48 return t.operation 49 } 50 51 func (t *TimeoutError) Error() string { return fmt.Sprintf("%v", t) } 52 53 // Format implements fmt.Formatter. 54 func (t *TimeoutError) Format(s fmt.State, verb rune) { errors.FormatError(t, s, verb) } 55 56 // SafeFormatError implements errors.SafeFormatter. 57 func (t *TimeoutError) SafeFormatError(p errors.Printer) (next error) { 58 // NB: With RunWithTimeout(), it is possible for both the caller and the 59 // callee to have set their own context timeout that is smaller than the 60 // timeout set by RunWithTimeout. It is also possible for the operation to run 61 // for much longer than the timeout, e.g. if the callee does not check the 62 // context in a timely manner. The error message must make this clear. 63 p.Printf("operation %q timed out", t.operation) 64 if t.took != 0 { 65 p.Printf(" after %s", t.took.Round(time.Millisecond)) 66 } 67 p.Printf(" (given timeout %s)", t.timeout) 68 return t.cause 69 } 70 71 // Timeout implements net.Error. 72 func (*TimeoutError) Timeout() bool { return true } 73 74 // Temporary implements net.Error. 75 func (*TimeoutError) Temporary() bool { return true } 76 77 // Cause implements Causer. 78 func (t *TimeoutError) Cause() error { 79 return t.cause 80 } 81 82 // encodeTimeoutError serializes a TimeoutError. 83 // We cannot include the operation in the safe strings because 84 // we currently have plenty of uses where the operation is constructed 85 // from unsafe/sensitive data. 86 func encodeTimeoutError( 87 _ context.Context, err error, 88 ) (msgPrefix string, safe []string, details proto.Message) { 89 t := err.(*TimeoutError) 90 details = &errorspb.StringsPayload{ 91 Details: []string{t.operation, t.timeout.String(), t.took.String()}, 92 } 93 msgPrefix = fmt.Sprintf("operation %q timed out after %s", t.operation, t.timeout) 94 return msgPrefix, nil, details 95 } 96 97 func decodeTimeoutError( 98 ctx context.Context, cause error, msgPrefix string, safeDetails []string, payload proto.Message, 99 ) error { 100 m, ok := payload.(*errorspb.StringsPayload) 101 if !ok || len(m.Details) < 2 { 102 // If this ever happens, this means some version of the library 103 // (presumably future) changed the payload type, and we're 104 // receiving this here. In this case, give up and let 105 // DecodeError use the opaque type. 106 return nil 107 } 108 op := m.Details[0] 109 timeout, decodeErr := time.ParseDuration(m.Details[1]) 110 if decodeErr != nil { 111 // Not encoded by our encode function. Bail out. 112 return nil //nolint:returnerrcheck 113 } 114 var took time.Duration 115 if len(m.Details) >= 3 { 116 took, decodeErr = time.ParseDuration(m.Details[2]) 117 if decodeErr != nil { 118 // Not encoded by our encode function. Bail out. 119 return nil //nolint:returnerrcheck 120 } 121 } 122 return &TimeoutError{ 123 operation: op, 124 timeout: timeout, 125 took: took, 126 cause: cause, 127 } 128 } 129 130 func init() { 131 errors.RegisterTypeMigration("github.com/cockroachdb/cockroachdb-parser/pkg/util/contextutil", 132 "*contextutil.TimeoutError", &TimeoutError{}) 133 134 pKey := errors.GetTypeKey(&TimeoutError{}) 135 errors.RegisterWrapperEncoder(pKey, encodeTimeoutError) 136 errors.RegisterWrapperDecoder(pKey, decodeTimeoutError) 137 138 }