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  }