github.com/yorinasub17/go-cloud@v0.27.40/internal/retry/retry.go (about)

     1  // Copyright 2018 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package retry provides retry logic.
    16  package retry // import "gocloud.dev/internal/retry"
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/googleapis/gax-go/v2"
    24  )
    25  
    26  // Call calls the supplied function f repeatedly, using the isRetryable function and
    27  // the provided backoff parameters to control the repetition.
    28  //
    29  // When f returns nil, Call immediately returns nil.
    30  //
    31  // When f returns an error for which isRetryable returns false, Call immediately
    32  // returns that error.
    33  //
    34  // When f returns an error for which isRetryable returns true, Call sleeps for the
    35  // provided backoff value and invokes f again.
    36  //
    37  // When the provided context is done, Retry returns a ContextError that includes both
    38  // ctx.Error() and the last error returned by f, or nil if there isn't one.
    39  func Call(ctx context.Context, bo gax.Backoff, isRetryable func(error) bool, f func() error) error {
    40  	return call(ctx, bo, isRetryable, f, gax.Sleep)
    41  }
    42  
    43  // Split out for testing.
    44  func call(ctx context.Context, bo gax.Backoff, isRetryable func(error) bool, f func() error,
    45  	sleep func(context.Context, time.Duration) error) error {
    46  	// Do nothing if context is done on entry.
    47  	if err := ctx.Err(); err != nil {
    48  		return &ContextError{CtxErr: err}
    49  	}
    50  	for {
    51  		err := f()
    52  		if err == nil {
    53  			return nil
    54  		}
    55  		if !isRetryable(err) {
    56  			return err
    57  		}
    58  		if cerr := sleep(ctx, bo.Pause()); cerr != nil {
    59  			return &ContextError{CtxErr: cerr, FuncErr: err}
    60  		}
    61  	}
    62  }
    63  
    64  // A ContextError contains both a context error (either context.Canceled or
    65  // context.DeadlineExceeded), and the last error from the function being retried,
    66  // or nil if the function was never called.
    67  type ContextError struct {
    68  	CtxErr  error // The error obtained from ctx.Err()
    69  	FuncErr error // The error obtained from the function being retried, or nil
    70  }
    71  
    72  func (e *ContextError) Error() string {
    73  	return fmt.Sprintf("%v; last error: %v", e.CtxErr, e.FuncErr)
    74  }
    75  
    76  // Is returns true iff one of the two errors held in e is equal to target.
    77  func (e *ContextError) Is(target error) bool {
    78  	return e.CtxErr == target || e.FuncErr == target
    79  }