|
@@ -5,9 +5,37 @@ package retrier
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"context"
|
|
"context"
|
|
|
|
|
+ "math/rand"
|
|
|
|
|
+ "sync"
|
|
|
"time"
|
|
"time"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+// Retrier implements the "retriable" resiliency pattern, abstracting out the process of retrying a failed action
|
|
|
|
|
+// a certain number of times with an optional back-off between each retry.
|
|
|
|
|
+type Retrier struct {
|
|
|
|
|
+ backoff []time.Duration
|
|
|
|
|
+ class Classifier
|
|
|
|
|
+ jitter float64
|
|
|
|
|
+ rand *rand.Rand
|
|
|
|
|
+ randMu sync.Mutex
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// New constructs a Retrier with the given backoff pattern and classifier. The length of the backoff pattern
|
|
|
|
|
+// indicates how many times an action will be retried, and the value at each index indicates the amount of time
|
|
|
|
|
+// waited before each subsequent retry. The classifier is used to determine which errors should be retried and
|
|
|
|
|
+// which should cause the retrier to fail fast. The DefaultClassifier is used if nil is passed.
|
|
|
|
|
+func New(backoff []time.Duration, class Classifier) *Retrier {
|
|
|
|
|
+ if class == nil {
|
|
|
|
|
+ class = DefaultClassifier{}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return &Retrier{
|
|
|
|
|
+ backoff: backoff,
|
|
|
|
|
+ class: class,
|
|
|
|
|
+ rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Run executes the given work function by executing RunCtx without context.Context.
|
|
// Run executes the given work function by executing RunCtx without context.Context.
|
|
|
func (r *Retrier) Run(work func() error) error {
|
|
func (r *Retrier) Run(work func() error) error {
|
|
|
return r.RunCtx(context.Background(), func(ctx context.Context) error {
|
|
return r.RunCtx(context.Background(), func(ctx context.Context) error {
|
|
@@ -52,3 +80,20 @@ func (r *Retrier) sleep(ctx context.Context, t <-chan time.Time) error {
|
|
|
return ctx.Err()
|
|
return ctx.Err()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+func (r *Retrier) calcSleep(i int) time.Duration {
|
|
|
|
|
+ // lock unsafe rand prng
|
|
|
|
|
+ r.randMu.Lock()
|
|
|
|
|
+ defer r.randMu.Unlock()
|
|
|
|
|
+ // take a random float in the range (-r.jitter, +r.jitter) and multiply it by the base amount
|
|
|
|
|
+ return r.backoff[i] + time.Duration(((r.rand.Float64()*2)-1)*r.jitter*float64(r.backoff[i]))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SetJitter sets the amount of jitter on each back-off to a factor between 0.0 and 1.0 (values outside this range
|
|
|
|
|
+// are silently ignored). When a retry occurs, the back-off is adjusted by a random amount up to this value.
|
|
|
|
|
+func (r *Retrier) SetJitter(jit float64) {
|
|
|
|
|
+ if jit < 0 || jit > 1 {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ r.jitter = jit
|
|
|
|
|
+}
|