go.mercari.io/datastore@v1.8.2/dsmiddleware/rpcretry/rpcretry.go (about)

     1  package rpcretry
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"time"
     7  
     8  	"go.mercari.io/datastore"
     9  	"google.golang.org/grpc/codes"
    10  	"google.golang.org/grpc/status"
    11  )
    12  
    13  var _ datastore.Middleware = &retryHandler{}
    14  
    15  // New automatically RPC retry middleware creates & returns.
    16  func New(opts ...RetryOption) datastore.Middleware {
    17  	rh := &retryHandler{
    18  		retryLimit:         3,
    19  		minBackoffDuration: 100 * time.Millisecond,
    20  		logf:               func(ctx context.Context, format string, args ...interface{}) {},
    21  	}
    22  
    23  	for _, opt := range opts {
    24  		opt.Apply(rh)
    25  	}
    26  
    27  	return rh
    28  }
    29  
    30  type retryHandler struct {
    31  	retryLimit         int
    32  	minBackoffDuration time.Duration
    33  	maxBackoffDuration time.Duration
    34  	maxDoublings       int
    35  	logf               func(ctx context.Context, format string, args ...interface{})
    36  }
    37  
    38  // A RetryOption is an retry option for a retry middleware.
    39  type RetryOption interface {
    40  	Apply(*retryHandler)
    41  }
    42  
    43  func (rh *retryHandler) waitDuration(retry int) time.Duration {
    44  	d := 10 * time.Millisecond
    45  	if 0 <= rh.minBackoffDuration {
    46  		d = rh.minBackoffDuration
    47  	}
    48  
    49  	m := retry
    50  	if 0 < rh.maxDoublings && rh.maxDoublings < m {
    51  		m = rh.maxDoublings
    52  	}
    53  	if m <= 0 {
    54  		m = 1
    55  	}
    56  
    57  	wait := math.Pow(2, float64(m-1)) * float64(d)
    58  
    59  	if 0 < rh.maxBackoffDuration {
    60  		wait = math.Min(wait, float64(rh.maxBackoffDuration))
    61  	}
    62  
    63  	return time.Duration(wait)
    64  }
    65  
    66  func (rh *retryHandler) try(ctx context.Context, logPrefix string, f func() error) {
    67  	try := 1
    68  	for {
    69  		err := f()
    70  		if err == nil {
    71  			return
    72  		} else if _, ok := err.(datastore.MultiError); ok {
    73  			// If MultiError returns, it should not be fixed even if it is retried
    74  			return
    75  		} else if err == context.DeadlineExceeded || err == context.Canceled {
    76  			// for appengine datastore
    77  			return
    78  		} else if code := status.Code(err); code != codes.Unknown {
    79  			// for cloud datastore
    80  			// https://cloud.google.com/datastore/docs/concepts/errors?hl=en#error_codes
    81  			switch code {
    82  			case codes.Aborted,
    83  				codes.AlreadyExists,
    84  				codes.FailedPrecondition,
    85  				codes.InvalidArgument,
    86  				codes.NotFound,
    87  				codes.PermissionDenied,
    88  				codes.Unauthenticated:
    89  				return
    90  			case codes.Internal:
    91  				if try != 1 {
    92  					// Do not retry this request more than once.
    93  					return
    94  				}
    95  			case codes.Canceled:
    96  				// not documented. but this error occurred by requester parameter.
    97  				return
    98  			default:
    99  				// retry
   100  			}
   101  		}
   102  
   103  		if rh.retryLimit <= try {
   104  			break
   105  		}
   106  
   107  		d := rh.waitDuration(try)
   108  		rh.logf(ctx, "%s: err=%s, will be retry #%d after %s", logPrefix, err.Error(), try, d.String())
   109  		t := time.NewTimer(d)
   110  		select {
   111  		case <-ctx.Done():
   112  			t.Stop()
   113  		case <-t.C:
   114  		}
   115  		try++
   116  	}
   117  }
   118  
   119  func (rh *retryHandler) AllocateIDs(info *datastore.MiddlewareInfo, keys []datastore.Key) (retKeys []datastore.Key, retErr error) {
   120  	next := info.Next
   121  	rh.try(info.Context, "middleware/rpcretry.AllocateIDs", func() error {
   122  		retKeys, retErr = next.AllocateIDs(info, keys)
   123  		return retErr
   124  	})
   125  	return
   126  }
   127  
   128  func (rh *retryHandler) PutMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) (retKeys []datastore.Key, retErr error) {
   129  	next := info.Next
   130  	rh.try(info.Context, "middleware/rpcretry.PutMultiWithoutTx", func() error {
   131  		retKeys, retErr = next.PutMultiWithoutTx(info, keys, psList)
   132  		return retErr
   133  	})
   134  	return
   135  }
   136  
   137  func (rh *retryHandler) PutMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) (retPKeys []datastore.PendingKey, retErr error) {
   138  	next := info.Next
   139  	rh.try(info.Context, "middleware/rpcretry.PutMultiWithTx", func() error {
   140  		retPKeys, retErr = next.PutMultiWithTx(info, keys, psList)
   141  		return retErr
   142  	})
   143  	return
   144  }
   145  
   146  func (rh *retryHandler) GetMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) (retErr error) {
   147  	next := info.Next
   148  	rh.try(info.Context, "middleware/rpcretry.GetMultiWithoutTx", func() error {
   149  		retErr = next.GetMultiWithoutTx(info, keys, psList)
   150  		return retErr
   151  	})
   152  	return
   153  }
   154  
   155  func (rh *retryHandler) GetMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) (retErr error) {
   156  	next := info.Next
   157  	rh.try(info.Context, "middleware/rpcretry.GetMultiWithTx", func() error {
   158  		retErr = next.GetMultiWithTx(info, keys, psList)
   159  		return retErr
   160  	})
   161  	return
   162  }
   163  
   164  func (rh *retryHandler) DeleteMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key) (retErr error) {
   165  	next := info.Next
   166  	rh.try(info.Context, "middleware/rpcretry.DeleteMultiWithoutTx", func() error {
   167  		retErr = next.DeleteMultiWithoutTx(info, keys)
   168  		return retErr
   169  	})
   170  	return
   171  }
   172  
   173  func (rh *retryHandler) DeleteMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key) (retErr error) {
   174  	next := info.Next
   175  	rh.try(info.Context, "middleware/rpcretry.DeleteMultiWithTx", func() error {
   176  		retErr = next.DeleteMultiWithTx(info, keys)
   177  		return retErr
   178  	})
   179  	return
   180  }
   181  
   182  func (rh *retryHandler) PostCommit(info *datastore.MiddlewareInfo, tx datastore.Transaction, commit datastore.Commit) (retErr error) {
   183  	return info.Next.PostCommit(info, tx, commit)
   184  }
   185  
   186  func (rh *retryHandler) PostRollback(info *datastore.MiddlewareInfo, tx datastore.Transaction) (retErr error) {
   187  	return info.Next.PostRollback(info, tx)
   188  }
   189  
   190  func (rh *retryHandler) Run(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump) datastore.Iterator {
   191  	return info.Next.Run(info, q, qDump)
   192  }
   193  
   194  func (rh *retryHandler) GetAll(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump, psList *[]datastore.PropertyList) (retKeys []datastore.Key, retErr error) {
   195  	next := info.Next
   196  	rh.try(info.Context, "middleware/rpcretry.GetAll", func() error {
   197  		retKeys, retErr = next.GetAll(info, q, qDump, psList)
   198  		return retErr
   199  	})
   200  	return
   201  }
   202  
   203  func (rh *retryHandler) Next(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump, iter datastore.Iterator, ps *datastore.PropertyList) (datastore.Key, error) {
   204  	// Next is not idempotent
   205  	return info.Next.Next(info, q, qDump, iter, ps)
   206  }
   207  
   208  func (rh *retryHandler) Count(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump) (retCnt int, retErr error) {
   209  	next := info.Next
   210  	rh.try(info.Context, "middleware/rpcretry.Count", func() error {
   211  		retCnt, retErr = next.Count(info, q, qDump)
   212  		return retErr
   213  	})
   214  	return
   215  }