107 lines
2.3 KiB
Go
107 lines
2.3 KiB
Go
package queue
|
|
|
|
import (
|
|
"hash/fnv"
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
type RetryAction string
|
|
|
|
const (
|
|
RetryActionRetry RetryAction = "retry"
|
|
RetryActionDeadLetter RetryAction = "dead_letter"
|
|
RetryActionAck RetryAction = "ack"
|
|
)
|
|
|
|
type RetryDecision struct {
|
|
Action RetryAction
|
|
Delay time.Duration
|
|
Reason string
|
|
}
|
|
|
|
type RetryPolicy interface {
|
|
Decide(delivery *Delivery, envelope *Envelope, err error) RetryDecision
|
|
}
|
|
|
|
type ExponentialBackoffPolicy struct {
|
|
minDelay time.Duration
|
|
maxDelay time.Duration
|
|
permanentFailureDelay time.Duration
|
|
}
|
|
|
|
func NewExponentialBackoffPolicy(minDelay, maxDelay, permanentFailureDelay time.Duration) *ExponentialBackoffPolicy {
|
|
if minDelay <= 0 {
|
|
minDelay = time.Second
|
|
}
|
|
if maxDelay < minDelay {
|
|
maxDelay = minDelay
|
|
}
|
|
if permanentFailureDelay <= 0 {
|
|
permanentFailureDelay = 30 * time.Second
|
|
}
|
|
return &ExponentialBackoffPolicy{
|
|
minDelay: minDelay,
|
|
maxDelay: maxDelay,
|
|
permanentFailureDelay: permanentFailureDelay,
|
|
}
|
|
}
|
|
|
|
func (p *ExponentialBackoffPolicy) Decide(delivery *Delivery, _ *Envelope, err error) RetryDecision {
|
|
if err == nil {
|
|
return RetryDecision{Action: RetryActionAck}
|
|
}
|
|
if IsPermanent(err) {
|
|
return RetryDecision{
|
|
Action: RetryActionDeadLetter,
|
|
Delay: p.permanentFailureDelay,
|
|
Reason: err.Error(),
|
|
}
|
|
}
|
|
attempt := 1
|
|
if delivery != nil && delivery.ReceiveCount > 0 {
|
|
attempt = delivery.ReceiveCount
|
|
}
|
|
delay := p.backoffWithJitter(deliveryID(delivery), attempt)
|
|
return RetryDecision{
|
|
Action: RetryActionRetry,
|
|
Delay: delay,
|
|
Reason: err.Error(),
|
|
}
|
|
}
|
|
|
|
func (p *ExponentialBackoffPolicy) backoffWithJitter(seed string, attempt int) time.Duration {
|
|
if attempt < 1 {
|
|
attempt = 1
|
|
}
|
|
power := math.Pow(2, float64(attempt-1))
|
|
delay := time.Duration(float64(p.minDelay) * power)
|
|
if delay > p.maxDelay {
|
|
delay = p.maxDelay
|
|
}
|
|
if delay <= 0 {
|
|
return p.minDelay
|
|
}
|
|
jitter := deterministicJitter(seed, delay/2)
|
|
return delay/2 + jitter
|
|
}
|
|
|
|
func deterministicJitter(seed string, max time.Duration) time.Duration {
|
|
if max <= 0 {
|
|
return 0
|
|
}
|
|
h := fnv.New64a()
|
|
_, _ = h.Write([]byte(seed))
|
|
return time.Duration(h.Sum64() % uint64(max))
|
|
}
|
|
|
|
func deliveryID(delivery *Delivery) string {
|
|
if delivery == nil {
|
|
return "unknown"
|
|
}
|
|
if delivery.ID != "" {
|
|
return delivery.ID
|
|
}
|
|
return "unknown"
|
|
}
|