blob: ddff081c8f342ba45c697e7675f907fc3dff0130 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package retry
import (
"math"
"math/rand"
"time"
)
// Stop indicates that no more retries should be made.
const Stop time.Duration = -1
type Backoff interface {
// Next gets the duration to wait before retrying the operation or |Stop|
// to indicate that no retries should be made.
Next() time.Duration
// Reset resets to initial state.
Reset()
}
// ZeroBackoff is a fixed policy whose back-off time is always zero, meaning
// that the operation is retried immediately without waiting.
type ZeroBackoff struct{}
func (b *ZeroBackoff) Reset() {}
func (b *ZeroBackoff) Next() time.Duration { return 0 }
// ConstantBackoff is a fixed policy that always returns the same backoff delay.
type ConstantBackoff struct {
interval time.Duration
}
func (b *ConstantBackoff) Reset() {}
func (b *ConstantBackoff) Next() time.Duration { return b.interval }
func NewConstantBackoff(d time.Duration) *ConstantBackoff {
return &ConstantBackoff{interval: d}
}
type maxAttemptsBackoff struct {
backOff Backoff
maxAttempts uint64
numAttempts uint64
}
func (b *maxAttemptsBackoff) Next() time.Duration {
if b.maxAttempts > 0 {
b.numAttempts++
if b.numAttempts >= b.maxAttempts {
return Stop
}
}
return b.backOff.Next()
}
func (b *maxAttemptsBackoff) Reset() {
b.numAttempts = 0
b.backOff.Reset()
}
// WithMaxAttempts wraps a back-off which stops after |max| attempts. If the max
// is 0, then it won't apply a maximum number attempts.
func WithMaxAttempts(b Backoff, max uint64) Backoff {
return &maxAttemptsBackoff{backOff: b, maxAttempts: max}
}
type maxDurationBackoff struct {
backOff Backoff
maxDuration time.Duration
startTime time.Time
c clock
}
func (b *maxDurationBackoff) Next() time.Duration {
if b.c.Since(b.startTime) < b.maxDuration {
return b.backOff.Next()
}
return Stop
}
func (b *maxDurationBackoff) Reset() {
b.startTime = b.c.Now()
b.backOff.Reset()
}
// WithMaxDuration wraps a back-off which stops attempting retries after |max|
// duration.
func WithMaxDuration(b Backoff, max time.Duration) Backoff {
return &maxDurationBackoff{backOff: b, maxDuration: max, c: &systemClock{}}
}
// ExponentialBackoff is a policy that increase the delay exponentially, with a
// small amount of randomness.
type ExponentialBackoff struct {
initialInterval time.Duration
maxInterval time.Duration
multiplier float64
iteration int
randObj *rand.Rand
}
// NewExponentialBackoff returns a new ExponentialBackoff object.
func NewExponentialBackoff(initialInterval time.Duration, maxInterval time.Duration, multiplier float64) *ExponentialBackoff {
return &ExponentialBackoff{
initialInterval: initialInterval,
maxInterval: maxInterval,
multiplier: multiplier,
iteration: 0,
randObj: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
func (e *ExponentialBackoff) Reset() {
e.iteration = 0
}
func (e *ExponentialBackoff) Next() time.Duration {
// Number of seconds to wait for the next attempt.
seconds := float64(e.initialInterval) / float64(time.Second) * math.Pow(e.multiplier, float64(e.iteration))
// Plus a random interval proportional to the number of seconds, with a
// ceiling.
seconds += math.Min(10.0, seconds/2) * e.randObj.Float64()
next := time.Duration(seconds * float64(time.Second))
if next > e.maxInterval {
return e.maxInterval
}
e.iteration++
return next
}
// NoRetries returns a backoff that will do a single attempt, with no retries.
func NoRetries() Backoff {
return WithMaxAttempts(&ZeroBackoff{}, 1)
}