mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-11-01 12:34:28 +00:00
118 lines
2.6 KiB
Go
118 lines
2.6 KiB
Go
|
// Package backoff provides an exponential-backoff implementation partially
|
||
|
// taken from jpillora/backoff.
|
||
|
package backoff
|
||
|
|
||
|
import (
|
||
|
"math"
|
||
|
"math/rand"
|
||
|
"sync/atomic"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
factor = 2
|
||
|
jitter = true
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
rand.Seed(time.Now().UnixNano())
|
||
|
}
|
||
|
|
||
|
// Timer is a backoff timer.
|
||
|
type Timer struct {
|
||
|
backoff Backoff
|
||
|
timer *time.Timer
|
||
|
}
|
||
|
|
||
|
// NewTimer returns a new uninitialized timer.
|
||
|
func NewTimer(min, max time.Duration) Timer {
|
||
|
return Timer{
|
||
|
backoff: NewBackoff(min, max),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Next initializes the timer if needed and returns a timer channel that fires
|
||
|
// when the backoff timeout is reached.
|
||
|
func (t *Timer) Next() <-chan time.Time {
|
||
|
if t.timer == nil {
|
||
|
t.timer = time.NewTimer(t.backoff.Next())
|
||
|
} else {
|
||
|
t.timer.Stop() // ensure drained
|
||
|
t.timer.Reset(t.backoff.Next())
|
||
|
}
|
||
|
|
||
|
return t.timer.C
|
||
|
}
|
||
|
|
||
|
// Stop stops the internal timer and frees its resources. It does nothing if the
|
||
|
// timer is uninitialized.
|
||
|
func (t *Timer) Stop() {
|
||
|
if t.timer == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if !t.timer.Stop() {
|
||
|
<-t.timer.C // drain
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Backoff is a time.Duration counter, starting at Min. After every call to
|
||
|
// the Duration method the current timing is multiplied by Factor, but it
|
||
|
// never exceeds Max.
|
||
|
type Backoff struct {
|
||
|
min, max float64 // seconds
|
||
|
attempt int32 // negative == max uint32
|
||
|
}
|
||
|
|
||
|
// NewBackoff creates a new backoff time.Duration counter.
|
||
|
func NewBackoff(min, max time.Duration) Backoff {
|
||
|
return Backoff{
|
||
|
min: min.Seconds(),
|
||
|
max: max.Seconds(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Next returns the next backoff duration.
|
||
|
func (b *Backoff) Next() time.Duration {
|
||
|
return b.forAttempt(atomic.AddInt32(&b.attempt, 1) - 1)
|
||
|
}
|
||
|
|
||
|
const maxInt64 = float64(math.MaxInt64 - 512)
|
||
|
|
||
|
// forAttempt returns the duration for a specific attempt. This is useful if
|
||
|
// you have a large number of independent Backoffs, but don't want use
|
||
|
// unnecessary memory storing the Backoff parameters per Backoff. The first
|
||
|
// attempt should be 0.
|
||
|
func (b *Backoff) forAttempt(attempt int32) time.Duration {
|
||
|
if b.min >= b.max {
|
||
|
// short-circuit
|
||
|
return duration(b.max)
|
||
|
}
|
||
|
|
||
|
// Ensure attempt never overflows.
|
||
|
if attempt < 0 {
|
||
|
attempt = math.MaxInt32
|
||
|
}
|
||
|
|
||
|
// Calculate this duration.
|
||
|
dur := b.min * math.Pow(factor, float64(attempt))
|
||
|
if jitter {
|
||
|
dur = rand.Float64()*(dur-b.min) + b.min
|
||
|
}
|
||
|
|
||
|
if dur < b.min {
|
||
|
return duration(b.min)
|
||
|
}
|
||
|
if dur > b.max {
|
||
|
return duration(b.max)
|
||
|
}
|
||
|
|
||
|
return duration(dur)
|
||
|
}
|
||
|
|
||
|
// duration converts a seconds float64 to time.Duration without losing accuracy.
|
||
|
func duration(secs float64) time.Duration {
|
||
|
int, frac := math.Modf(secs)
|
||
|
return (time.Duration(int) * time.Second) + time.Duration(frac*float64(time.Second))
|
||
|
}
|