invoke.go raw

   1  // Copyright 2016, Google Inc.
   2  // All rights reserved.
   3  //
   4  // Redistribution and use in source and binary forms, with or without
   5  // modification, are permitted provided that the following conditions are
   6  // met:
   7  //
   8  //     * Redistributions of source code must retain the above copyright
   9  // notice, this list of conditions and the following disclaimer.
  10  //     * Redistributions in binary form must reproduce the above
  11  // copyright notice, this list of conditions and the following disclaimer
  12  // in the documentation and/or other materials provided with the
  13  // distribution.
  14  //     * Neither the name of Google Inc. nor the names of its
  15  // contributors may be used to endorse or promote products derived from
  16  // this software without specific prior written permission.
  17  //
  18  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29  
  30  package gax
  31  
  32  import (
  33  	"context"
  34  	"strings"
  35  	"time"
  36  
  37  	"github.com/googleapis/gax-go/v2/apierror"
  38  )
  39  
  40  // APICall is a user defined call stub.
  41  type APICall func(context.Context, CallSettings) error
  42  
  43  // Invoke calls the given APICall, performing retries as specified by opts, if
  44  // any.
  45  func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
  46  	var settings CallSettings
  47  	for _, opt := range opts {
  48  		opt.Resolve(&settings)
  49  	}
  50  	return invoke(ctx, call, settings, Sleep)
  51  }
  52  
  53  // Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
  54  // If interrupted, Sleep returns ctx.Err().
  55  func Sleep(ctx context.Context, d time.Duration) error {
  56  	t := time.NewTimer(d)
  57  	select {
  58  	case <-ctx.Done():
  59  		t.Stop()
  60  		return ctx.Err()
  61  	case <-t.C:
  62  		return nil
  63  	}
  64  }
  65  
  66  type sleeper func(ctx context.Context, d time.Duration) error
  67  
  68  // invoke implements Invoke, taking an additional sleeper argument for testing.
  69  func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
  70  	var retryer Retryer
  71  
  72  	// Only use the value provided via WithTimeout if the context doesn't
  73  	// already have a deadline. This is important for backwards compatibility if
  74  	// the user already set a deadline on the context given to Invoke.
  75  	if _, ok := ctx.Deadline(); !ok && settings.timeout != 0 {
  76  		c, cc := context.WithTimeout(ctx, settings.timeout)
  77  		defer cc()
  78  		ctx = c
  79  	}
  80  
  81  	for {
  82  		err := call(ctx, settings)
  83  		if err == nil {
  84  			return nil
  85  		}
  86  		// Never retry permanent certificate errors. (e.x. if ca-certificates
  87  		// are not installed). We should only make very few, targeted
  88  		// exceptions: many (other) status=Unavailable should be retried, such
  89  		// as if there's a network hiccup, or the internet goes out for a
  90  		// minute. This is also why here we are doing string parsing instead of
  91  		// simply making Unavailable a non-retried code elsewhere.
  92  		if strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
  93  			return err
  94  		}
  95  		if apierr, ok := apierror.FromError(err); ok {
  96  			err = apierr
  97  		}
  98  		if settings.Retry == nil {
  99  			return err
 100  		}
 101  		if retryer == nil {
 102  			if r := settings.Retry(); r != nil {
 103  				retryer = r
 104  			} else {
 105  				return err
 106  			}
 107  		}
 108  		if d, ok := retryer.Retry(err); !ok {
 109  			return err
 110  		} else if err = sp(ctx, d); err != nil {
 111  			return err
 112  		}
 113  	}
 114  }
 115