init push
This commit is contained in:
106
internal/queue/retry.go
Normal file
106
internal/queue/retry.go
Normal file
@@ -0,0 +1,106 @@
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user