storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/madmin/retry.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2019-2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package madmin
    19  
    20  import (
    21  	"context"
    22  	"math/rand"
    23  	"net/http"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  // MaxRetry is the maximum number of retries before stopping.
    29  var MaxRetry = 10
    30  
    31  // MaxJitter will randomize over the full exponential backoff time
    32  const MaxJitter = 1.0
    33  
    34  // NoJitter disables the use of jitter for randomizing the exponential backoff time
    35  const NoJitter = 0.0
    36  
    37  // DefaultRetryUnit - default unit multiplicative per retry.
    38  // defaults to 1 second.
    39  const DefaultRetryUnit = time.Second
    40  
    41  // DefaultRetryCap - Each retry attempt never waits no longer than
    42  // this maximum time duration.
    43  const DefaultRetryCap = time.Second * 30
    44  
    45  // lockedRandSource provides protected rand source, implements rand.Source interface.
    46  type lockedRandSource struct {
    47  	lk  sync.Mutex
    48  	src rand.Source
    49  }
    50  
    51  // Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
    52  func (r *lockedRandSource) Int63() (n int64) {
    53  	r.lk.Lock()
    54  	n = r.src.Int63()
    55  	r.lk.Unlock()
    56  	return
    57  }
    58  
    59  // Seed uses the provided seed value to initialize the generator to a
    60  // deterministic state.
    61  func (r *lockedRandSource) Seed(seed int64) {
    62  	r.lk.Lock()
    63  	r.src.Seed(seed)
    64  	r.lk.Unlock()
    65  }
    66  
    67  // newRetryTimer creates a timer with exponentially increasing
    68  // delays until the maximum retry attempts are reached.
    69  func (adm AdminClient) newRetryTimer(ctx context.Context, maxRetry int, unit time.Duration, cap time.Duration, jitter float64) <-chan int {
    70  	attemptCh := make(chan int)
    71  
    72  	// computes the exponential backoff duration according to
    73  	// https://www.awsarchitectureblog.com/2015/03/backoff.html
    74  	exponentialBackoffWait := func(attempt int) time.Duration {
    75  		// normalize jitter to the range [0, 1.0]
    76  		if jitter < NoJitter {
    77  			jitter = NoJitter
    78  		}
    79  		if jitter > MaxJitter {
    80  			jitter = MaxJitter
    81  		}
    82  
    83  		//sleep = random_between(0, min(cap, base * 2 ** attempt))
    84  		sleep := unit * 1 << uint(attempt)
    85  		if sleep > cap {
    86  			sleep = cap
    87  		}
    88  		if jitter > NoJitter {
    89  			sleep -= time.Duration(adm.random.Float64() * float64(sleep) * jitter)
    90  		}
    91  		return sleep
    92  	}
    93  
    94  	go func() {
    95  		defer close(attemptCh)
    96  		for i := 0; i < maxRetry; i++ {
    97  			// Attempts start from 1.
    98  			select {
    99  			case attemptCh <- i + 1:
   100  			case <-ctx.Done():
   101  				// Stop the routine.
   102  				return
   103  			}
   104  
   105  			select {
   106  			case <-time.After(exponentialBackoffWait(i)):
   107  			case <-ctx.Done():
   108  				// Stop the routine.
   109  				return
   110  			}
   111  		}
   112  	}()
   113  	return attemptCh
   114  }
   115  
   116  // List of AWS S3 error codes which are retryable.
   117  var retryableS3Codes = map[string]struct{}{
   118  	"RequestError":         {},
   119  	"RequestTimeout":       {},
   120  	"Throttling":           {},
   121  	"ThrottlingException":  {},
   122  	"RequestLimitExceeded": {},
   123  	"RequestThrottled":     {},
   124  	"InternalError":        {},
   125  	"SlowDown":             {},
   126  	// Add more AWS S3 codes here.
   127  }
   128  
   129  // isS3CodeRetryable - is s3 error code retryable.
   130  func isS3CodeRetryable(s3Code string) (ok bool) {
   131  	_, ok = retryableS3Codes[s3Code]
   132  	return ok
   133  }
   134  
   135  // List of HTTP status codes which are retryable.
   136  var retryableHTTPStatusCodes = map[int]struct{}{
   137  	http.StatusRequestTimeout:      {},
   138  	http.StatusTooManyRequests:     {},
   139  	http.StatusInternalServerError: {},
   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  }