github.com/vmware/govmomi@v0.51.0/vim25/retry.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package vim25
     6  
     7  import (
     8  	"context"
     9  	"time"
    10  
    11  	"github.com/vmware/govmomi/vim25/soap"
    12  )
    13  
    14  type RetryFunc func(err error) (retry bool, delay time.Duration)
    15  
    16  // TemporaryNetworkError is deprecated. Use Retry() with RetryTemporaryNetworkError and retryAttempts instead.
    17  func TemporaryNetworkError(n int) RetryFunc {
    18  	return func(err error) (bool, time.Duration) {
    19  		if IsTemporaryNetworkError(err) {
    20  			// Don't retry if we're out of tries.
    21  			if n--; n <= 0 {
    22  				return false, 0
    23  			}
    24  			return true, 0
    25  		}
    26  		return false, 0
    27  	}
    28  }
    29  
    30  // RetryTemporaryNetworkError returns a RetryFunc that returns IsTemporaryNetworkError(err)
    31  func RetryTemporaryNetworkError(err error) (bool, time.Duration) {
    32  	return IsTemporaryNetworkError(err), 0
    33  }
    34  
    35  // IsTemporaryNetworkError returns false unless the error implements
    36  // a Temporary() bool method such as url.Error and net.Error.
    37  // Otherwise, returns the value of the Temporary() method.
    38  func IsTemporaryNetworkError(err error) bool {
    39  	t, ok := err.(interface {
    40  		// Temporary is implemented by url.Error and net.Error
    41  		Temporary() bool
    42  	})
    43  
    44  	if !ok {
    45  		// Not a Temporary error.
    46  		return false
    47  	}
    48  
    49  	return t.Temporary()
    50  }
    51  
    52  type retry struct {
    53  	roundTripper soap.RoundTripper
    54  
    55  	// fn is a custom function that is called when an error occurs.
    56  	// It returns whether or not to retry, and if so, how long to
    57  	// delay before retrying.
    58  	fn               RetryFunc
    59  	maxRetryAttempts int
    60  }
    61  
    62  // Retry wraps the specified soap.RoundTripper and invokes the
    63  // specified RetryFunc. The RetryFunc returns whether or not to
    64  // retry the call, and if so, how long to wait before retrying. If
    65  // the result of this function is to not retry, the original error
    66  // is returned from the RoundTrip function.
    67  // The soap.RoundTripper will return the original error if retryAttempts is specified and reached.
    68  func Retry(roundTripper soap.RoundTripper, fn RetryFunc, retryAttempts ...int) soap.RoundTripper {
    69  	r := &retry{
    70  		roundTripper:     roundTripper,
    71  		fn:               fn,
    72  		maxRetryAttempts: 1,
    73  	}
    74  
    75  	if len(retryAttempts) == 1 {
    76  		r.maxRetryAttempts = retryAttempts[0]
    77  	}
    78  
    79  	return r
    80  }
    81  
    82  func (r *retry) RoundTrip(ctx context.Context, req, res soap.HasFault) error {
    83  	var err error
    84  
    85  	for attempt := 0; attempt < r.maxRetryAttempts; attempt++ {
    86  		err = r.roundTripper.RoundTrip(ctx, req, res)
    87  		if err == nil {
    88  			break
    89  		}
    90  
    91  		// Invoke retry function to see if another attempt should be made.
    92  		if retry, delay := r.fn(err); retry {
    93  			time.Sleep(delay)
    94  			continue
    95  		}
    96  
    97  		break
    98  	}
    99  
   100  	return err
   101  }