github.com/minio/madmin-go/v2@v2.2.1/retry.go (about) 1 // 2 // Copyright (c) 2015-2022 MinIO, Inc. 3 // 4 // This file is part of MinIO Object Storage stack 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU Affero General Public License as 8 // published by the Free Software Foundation, either version 3 of the 9 // License, or (at your option) any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU Affero General Public License for more details. 15 // 16 // You should have received a copy of the GNU Affero General Public License 17 // along with this program. If not, see <http://www.gnu.org/licenses/>. 18 // 19 20 package madmin 21 22 import ( 23 "context" 24 "math/rand" 25 "net/http" 26 "sync" 27 "time" 28 ) 29 30 // MaxRetry is the maximum number of retries before stopping. 31 var MaxRetry = 10 32 33 // MaxJitter will randomize over the full exponential backoff time 34 const MaxJitter = 1.0 35 36 // NoJitter disables the use of jitter for randomizing the exponential backoff time 37 const NoJitter = 0.0 38 39 // DefaultRetryUnit - default unit multiplicative per retry. 40 // defaults to 1 second. 41 const DefaultRetryUnit = time.Second 42 43 // DefaultRetryCap - Each retry attempt never waits no longer than 44 // this maximum time duration. 45 const DefaultRetryCap = time.Second * 30 46 47 // lockedRandSource provides protected rand source, implements rand.Source interface. 48 type lockedRandSource struct { 49 lk sync.Mutex 50 src rand.Source 51 } 52 53 // Int63 returns a non-negative pseudo-random 63-bit integer as an int64. 54 func (r *lockedRandSource) Int63() (n int64) { 55 r.lk.Lock() 56 n = r.src.Int63() 57 r.lk.Unlock() 58 return 59 } 60 61 // Seed uses the provided seed value to initialize the generator to a 62 // deterministic state. 63 func (r *lockedRandSource) Seed(seed int64) { 64 r.lk.Lock() 65 r.src.Seed(seed) 66 r.lk.Unlock() 67 } 68 69 // newRetryTimer creates a timer with exponentially increasing 70 // delays until the maximum retry attempts are reached. 71 func (adm AdminClient) newRetryTimer(ctx context.Context, maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int { 72 attemptCh := make(chan int) 73 74 // computes the exponential backoff duration according to 75 // https://www.awsarchitectureblog.com/2015/03/backoff.html 76 exponentialBackoffWait := func(attempt int) time.Duration { 77 // normalize jitter to the range [0, 1.0] 78 if jitter < NoJitter { 79 jitter = NoJitter 80 } 81 if jitter > MaxJitter { 82 jitter = MaxJitter 83 } 84 85 // sleep = random_between(0, min(cap, base * 2 ** attempt)) 86 sleep := unit * 1 << uint(attempt) 87 if sleep > cap { 88 sleep = cap 89 } 90 if jitter > NoJitter { 91 sleep -= time.Duration(adm.random.Float64() * float64(sleep) * jitter) 92 } 93 return sleep 94 } 95 96 go func() { 97 defer close(attemptCh) 98 for i := 0; i < maxRetry; i++ { 99 // Attempts start from 1. 100 select { 101 case attemptCh <- i + 1: 102 case <-ctx.Done(): 103 // Stop the routine. 104 return 105 } 106 107 select { 108 case <-time.After(exponentialBackoffWait(i)): 109 case <-ctx.Done(): 110 // Stop the routine. 111 return 112 } 113 } 114 }() 115 return attemptCh 116 } 117 118 // List of admin error codes which are retryable. 119 var retryableAdminErrCodes = map[string]struct{}{ 120 "RequestError": {}, 121 "RequestTimeout": {}, 122 "Throttling": {}, 123 "ThrottlingException": {}, 124 "RequestLimitExceeded": {}, 125 "RequestThrottled": {}, 126 "SlowDown": {}, 127 // Add more admin error codes here. 128 } 129 130 // isAdminErrCodeRetryable - is admin error code retryable. 131 func isAdminErrCodeRetryable(code string) (ok bool) { 132 _, ok = retryableAdminErrCodes[code] 133 return ok 134 } 135 136 // List of HTTP status codes which are retryable. 137 var retryableHTTPStatusCodes = map[int]struct{}{ 138 http.StatusRequestTimeout: {}, 139 http.StatusTooManyRequests: {}, 140 http.StatusBadGateway: {}, 141 http.StatusServiceUnavailable: {}, 142 // Add more HTTP status codes here. 143 } 144 145 // isHTTPStatusRetryable - is HTTP error code retryable. 146 func isHTTPStatusRetryable(httpStatusCode int) (ok bool) { 147 _, ok = retryableHTTPStatusCodes[httpStatusCode] 148 return ok 149 }