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  }