retry.go (4270B)
1 /* 2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage 3 * Copyright 2015-2017 MinIO, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package minio 19 20 import ( 21 "context" 22 "crypto/x509" 23 "errors" 24 "net/http" 25 "net/url" 26 "time" 27 ) 28 29 // MaxRetry is the maximum number of retries before stopping. 30 var MaxRetry = 10 31 32 // MaxJitter will randomize over the full exponential backoff time 33 const MaxJitter = 1.0 34 35 // NoJitter disables the use of jitter for randomizing the exponential backoff time 36 const NoJitter = 0.0 37 38 // DefaultRetryUnit - default unit multiplicative per retry. 39 // defaults to 200 * time.Millisecond 40 var DefaultRetryUnit = 200 * time.Millisecond 41 42 // DefaultRetryCap - Each retry attempt never waits no longer than 43 // this maximum time duration. 44 var DefaultRetryCap = time.Second 45 46 // newRetryTimer creates a timer with exponentially increasing 47 // delays until the maximum retry attempts are reached. 48 func (c *Client) newRetryTimer(ctx context.Context, maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int { 49 attemptCh := make(chan int) 50 51 // computes the exponential backoff duration according to 52 // https://www.awsarchitectureblog.com/2015/03/backoff.html 53 exponentialBackoffWait := func(attempt int) time.Duration { 54 // normalize jitter to the range [0, 1.0] 55 if jitter < NoJitter { 56 jitter = NoJitter 57 } 58 if jitter > MaxJitter { 59 jitter = MaxJitter 60 } 61 62 // sleep = random_between(0, min(cap, base * 2 ** attempt)) 63 sleep := unit * time.Duration(1<<uint(attempt)) 64 if sleep > cap { 65 sleep = cap 66 } 67 if jitter != NoJitter { 68 sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter) 69 } 70 return sleep 71 } 72 73 go func() { 74 defer close(attemptCh) 75 for i := 0; i < maxRetry; i++ { 76 select { 77 case attemptCh <- i + 1: 78 case <-ctx.Done(): 79 return 80 } 81 82 select { 83 case <-time.After(exponentialBackoffWait(i)): 84 case <-ctx.Done(): 85 return 86 } 87 } 88 }() 89 return attemptCh 90 } 91 92 // List of AWS S3 error codes which are retryable. 93 var retryableS3Codes = map[string]struct{}{ 94 "RequestError": {}, 95 "RequestTimeout": {}, 96 "Throttling": {}, 97 "ThrottlingException": {}, 98 "RequestLimitExceeded": {}, 99 "RequestThrottled": {}, 100 "InternalError": {}, 101 "ExpiredToken": {}, 102 "ExpiredTokenException": {}, 103 "SlowDown": {}, 104 // Add more AWS S3 codes here. 105 } 106 107 // isS3CodeRetryable - is s3 error code retryable. 108 func isS3CodeRetryable(s3Code string) (ok bool) { 109 _, ok = retryableS3Codes[s3Code] 110 return ok 111 } 112 113 // List of HTTP status codes which are retryable. 114 var retryableHTTPStatusCodes = map[int]struct{}{ 115 429: {}, // http.StatusTooManyRequests is not part of the Go 1.5 library, yet 116 499: {}, // client closed request, retry. A non-standard status code introduced by nginx. 117 http.StatusInternalServerError: {}, 118 http.StatusBadGateway: {}, 119 http.StatusServiceUnavailable: {}, 120 http.StatusGatewayTimeout: {}, 121 // Add more HTTP status codes here. 122 } 123 124 // isHTTPStatusRetryable - is HTTP error code retryable. 125 func isHTTPStatusRetryable(httpStatusCode int) (ok bool) { 126 _, ok = retryableHTTPStatusCodes[httpStatusCode] 127 return ok 128 } 129 130 // For now, all http Do() requests are retriable except some well defined errors 131 func isRequestErrorRetryable(err error) bool { 132 if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { 133 return false 134 } 135 if ue, ok := err.(*url.Error); ok { 136 e := ue.Unwrap() 137 switch e.(type) { 138 // x509: certificate signed by unknown authority 139 case x509.UnknownAuthorityError: 140 return false 141 } 142 switch e.Error() { 143 case "http: server gave HTTP response to HTTPS client": 144 return false 145 } 146 } 147 return true 148 }