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
18package minio
19
20import (
21	"context"
22	"net/http"
23	"time"
24)
25
26// MaxRetry is the maximum number of retries before stopping.
27var MaxRetry = 10
28
29// MaxJitter will randomize over the full exponential backoff time
30const MaxJitter = 1.0
31
32// NoJitter disables the use of jitter for randomizing the exponential backoff time
33const NoJitter = 0.0
34
35// DefaultRetryUnit - default unit multiplicative per retry.
36// defaults to 200 * time.Millisecond
37var DefaultRetryUnit = 200 * time.Millisecond
38
39// DefaultRetryCap - Each retry attempt never waits no longer than
40// this maximum time duration.
41var DefaultRetryCap = time.Second
42
43// newRetryTimer creates a timer with exponentially increasing
44// delays until the maximum retry attempts are reached.
45func (c *Client) newRetryTimer(ctx context.Context, maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int {
46	attemptCh := make(chan int)
47
48	// computes the exponential backoff duration according to
49	// https://www.awsarchitectureblog.com/2015/03/backoff.html
50	exponentialBackoffWait := func(attempt int) time.Duration {
51		// normalize jitter to the range [0, 1.0]
52		if jitter < NoJitter {
53			jitter = NoJitter
54		}
55		if jitter > MaxJitter {
56			jitter = MaxJitter
57		}
58
59		// sleep = random_between(0, min(cap, base * 2 ** attempt))
60		sleep := unit * time.Duration(1<<uint(attempt))
61		if sleep > cap {
62			sleep = cap
63		}
64		if jitter != NoJitter {
65			sleep -= time.Duration(c.random.Float64() * float64(sleep) * jitter)
66		}
67		return sleep
68	}
69
70	go func() {
71		defer close(attemptCh)
72		for i := 0; i < maxRetry; i++ {
73			select {
74			case attemptCh <- i + 1:
75			case <-ctx.Done():
76				return
77			}
78
79			select {
80			case <-time.After(exponentialBackoffWait(i)):
81			case <-ctx.Done():
82				return
83			}
84		}
85	}()
86	return attemptCh
87}
88
89// List of AWS S3 error codes which are retryable.
90var retryableS3Codes = map[string]struct{}{
91	"RequestError":          {},
92	"RequestTimeout":        {},
93	"Throttling":            {},
94	"ThrottlingException":   {},
95	"RequestLimitExceeded":  {},
96	"RequestThrottled":      {},
97	"InternalError":         {},
98	"ExpiredToken":          {},
99	"ExpiredTokenException": {},
100	"SlowDown":              {},
101	// Add more AWS S3 codes here.
102}
103
104// isS3CodeRetryable - is s3 error code retryable.
105func isS3CodeRetryable(s3Code string) (ok bool) {
106	_, ok = retryableS3Codes[s3Code]
107	return ok
108}
109
110// List of HTTP status codes which are retryable.
111var retryableHTTPStatusCodes = map[int]struct{}{
112	429:                            {}, // http.StatusTooManyRequests is not part of the Go 1.5 library, yet
113	http.StatusInternalServerError: {},
114	http.StatusBadGateway:          {},
115	http.StatusServiceUnavailable:  {},
116	http.StatusGatewayTimeout:      {},
117	// Add more HTTP status codes here.
118}
119
120// isHTTPStatusRetryable - is HTTP error code retryable.
121func isHTTPStatusRetryable(httpStatusCode int) (ok bool) {
122	_, ok = retryableHTTPStatusCodes[httpStatusCode]
123	return ok
124}
125