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 //------------------------------------------------------------------------------