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 }