github.com/Jeffail/benthos/v3@v3.65.0/lib/util/throttle/type.go (about)

     1  package throttle
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  	"time"
     7  )
     8  
     9  //------------------------------------------------------------------------------
    10  
    11  // Type is a throttle of retries to avoid endless busy loops when a message
    12  // fails to reach its destination.
    13  type Type struct {
    14  	// consecutiveRetries is the live count of consecutive retries.
    15  	consecutiveRetries int64
    16  
    17  	// throttlePeriod is the current throttle period, by default this is set to
    18  	// the baseThrottlePeriod.
    19  	throttlePeriod int64
    20  
    21  	// unthrottledRetries is the number of concecutive retries we are
    22  	// comfortable attempting before throttling begins.
    23  	unthrottledRetries int64
    24  
    25  	// maxExponentialPeriod is the maximum duration for which our throttle lasts
    26  	// when exponentially increasing.
    27  	maxExponentialPeriod int64
    28  
    29  	// baseThrottlePeriod is the static duration for which our throttle lasts.
    30  	baseThrottlePeriod int64
    31  
    32  	// closeChan can interrupt a throttle when closed.
    33  	closeChan <-chan struct{}
    34  }
    35  
    36  // New creates a new throttle, which permits a static number of consecutive
    37  // retries before throttling subsequent retries. A success will reset the count
    38  // of consecutive retries.
    39  func New(options ...func(*Type)) *Type {
    40  	t := &Type{
    41  		unthrottledRetries:   3,
    42  		baseThrottlePeriod:   int64(time.Second),
    43  		maxExponentialPeriod: int64(time.Minute),
    44  		closeChan:            nil,
    45  	}
    46  	t.throttlePeriod = t.baseThrottlePeriod
    47  	for _, option := range options {
    48  		option(t)
    49  	}
    50  	return t
    51  }
    52  
    53  //------------------------------------------------------------------------------
    54  
    55  // OptMaxUnthrottledRetries sets the maximum number of consecutive retries that
    56  // will be attempted before throttling will begin.
    57  func OptMaxUnthrottledRetries(n int64) func(*Type) {
    58  	return func(t *Type) {
    59  		t.unthrottledRetries = n
    60  	}
    61  }
    62  
    63  // OptMaxExponentPeriod sets the maximum period of time that throttles will last
    64  // when exponentially increasing.
    65  func OptMaxExponentPeriod(period time.Duration) func(*Type) {
    66  	return func(t *Type) {
    67  		t.maxExponentialPeriod = int64(period)
    68  	}
    69  }
    70  
    71  // OptThrottlePeriod sets the static period of time that throttles will last.
    72  func OptThrottlePeriod(period time.Duration) func(*Type) {
    73  	return func(t *Type) {
    74  		t.baseThrottlePeriod = int64(period)
    75  		t.throttlePeriod = int64(period)
    76  	}
    77  }
    78  
    79  // OptCloseChan sets a read-only channel that, if closed, will interrupt a retry
    80  // throttle early.
    81  func OptCloseChan(c <-chan struct{}) func(*Type) {
    82  	return func(t *Type) {
    83  		t.closeChan = c
    84  	}
    85  }
    86  
    87  //------------------------------------------------------------------------------
    88  
    89  // Retry indicates that a retry is about to occur and, if appropriate, will
    90  // block until either the throttle period is over and the retry may be attempted
    91  // (returning true) or that the close channel has closed (returning false).
    92  func (t *Type) Retry() bool {
    93  	return t.RetryWithContext(context.Background())
    94  }
    95  
    96  // RetryWithContext indicates that a retry is about to occur and, if
    97  // appropriate, will block until either the throttle period is over and the
    98  // retry may be attempted (returning true) or that the close channel has closed
    99  // (returning false), or that the context was cancelled (false).
   100  func (t *Type) RetryWithContext(ctx context.Context) bool {
   101  	if rets := atomic.AddInt64(&t.consecutiveRetries, 1); rets <= t.unthrottledRetries {
   102  		return true
   103  	}
   104  	select {
   105  	case <-time.After(time.Duration(atomic.LoadInt64(&t.throttlePeriod))):
   106  	case <-t.closeChan:
   107  		return false
   108  	case <-ctx.Done():
   109  		return false
   110  	}
   111  	return true
   112  }
   113  
   114  // ExponentialRetry is the same as Retry except also sets the throttle period to
   115  // exponentially increase after each consecutive retry.
   116  func (t *Type) ExponentialRetry() bool {
   117  	return t.ExponentialRetryWithContext(context.Background())
   118  }
   119  
   120  // ExponentialRetryWithContext is the same as RetryWithContext except also sets
   121  // the throttle period to exponentially increase after each consecutive retry.
   122  func (t *Type) ExponentialRetryWithContext(ctx context.Context) bool {
   123  	if atomic.LoadInt64(&t.consecutiveRetries) > t.unthrottledRetries {
   124  		if throtPrd := atomic.LoadInt64(&t.throttlePeriod); throtPrd < t.maxExponentialPeriod {
   125  			throtPrd *= 2
   126  			if throtPrd > t.maxExponentialPeriod {
   127  				throtPrd = t.maxExponentialPeriod
   128  			}
   129  			atomic.StoreInt64(&t.throttlePeriod, throtPrd)
   130  		}
   131  	}
   132  	return t.RetryWithContext(ctx)
   133  }
   134  
   135  // Reset clears the count of consecutive retries and resets the exponential
   136  // backoff.
   137  func (t *Type) Reset() {
   138  	atomic.StoreInt64(&t.consecutiveRetries, 0)
   139  	atomic.StoreInt64(&t.throttlePeriod, t.baseThrottlePeriod)
   140  }
   141  
   142  //------------------------------------------------------------------------------