mirror of
https://github.com/hanzo-fi/webhooks.git
synced 2026-10-30 20:32:55 +00:00
84 lines
2.2 KiB
Go
84 lines
2.2 KiB
Go
package backoff
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
webhooks "github.com/hanzo-fi/webhooks/pkg"
|
|
)
|
|
|
|
var ErrMaxAttemptsReached = errors.New("max attempts reached")
|
|
|
|
// NewExponential builds an exponential backoff policy. maxAttempts is a hard cap
|
|
// on the number of attempts: once reached, GetRetryDelay returns
|
|
// ErrMaxAttemptsReached regardless of the abort-after window. A value <= 0
|
|
// disables the cap (abort-after remains the only bound). The cap exists because
|
|
// abort-after alone produced ~724 attempts per dead endpoint in production.
|
|
func NewExponential(minRetryDelay, maxRetryDelay, abortAfterDelay time.Duration, maxAttempts int) webhooks.BackoffPolicy {
|
|
return &exponential{
|
|
minRetryDelay,
|
|
maxRetryDelay,
|
|
abortAfterDelay,
|
|
maxAttempts,
|
|
}
|
|
}
|
|
|
|
type exponential struct {
|
|
minRetryDelay time.Duration
|
|
maxRetryDelay time.Duration
|
|
abortAfterDelay time.Duration
|
|
maxAttempts int
|
|
}
|
|
|
|
func (e *exponential) GetRetryDelay(attemptNumber int) (time.Duration, error) {
|
|
if e.maxAttempts > 0 && attemptNumber+1 >= e.maxAttempts {
|
|
return 0, ErrMaxAttemptsReached
|
|
}
|
|
|
|
delay := e.retryDelayForAttempt(attemptNumber)
|
|
if e.elapsedBeforeAttempt(attemptNumber)+delay > e.abortAfterDelay {
|
|
return 0, ErrMaxAttemptsReached
|
|
}
|
|
return delay, nil
|
|
}
|
|
|
|
func (e *exponential) CanRetryAttempt(attemptNumber int) error {
|
|
if e.maxAttempts > 0 && attemptNumber >= e.maxAttempts {
|
|
return ErrMaxAttemptsReached
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *exponential) LimitRetryDelay(attemptNumber int, delay time.Duration) (time.Duration, error) {
|
|
if e.elapsedBeforeAttempt(attemptNumber)+delay > e.abortAfterDelay {
|
|
return 0, ErrMaxAttemptsReached
|
|
}
|
|
return delay, nil
|
|
}
|
|
|
|
func (e *exponential) LimitRetryWindow(elapsed time.Duration) error {
|
|
if elapsed > e.abortAfterDelay {
|
|
return ErrMaxAttemptsReached
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *exponential) retryDelayForAttempt(attemptNumber int) time.Duration {
|
|
delay := e.minRetryDelay
|
|
for i := 0; i < attemptNumber; i++ {
|
|
delay <<= 1
|
|
if delay > e.maxRetryDelay {
|
|
delay = e.maxRetryDelay
|
|
}
|
|
}
|
|
return delay
|
|
}
|
|
|
|
func (e *exponential) elapsedBeforeAttempt(attemptNumber int) time.Duration {
|
|
var elapsed time.Duration
|
|
for i := 0; i < attemptNumber; i++ {
|
|
elapsed += e.retryDelayForAttempt(i)
|
|
}
|
|
return elapsed
|
|
}
|