mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-18 22:04:41 +08:00
135 lines
3.2 KiB
Go
135 lines
3.2 KiB
Go
package retry
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Backoff is an interface that backs off.
|
|
type Backoff interface {
|
|
// Next returns the time duration to wait and whether to stop.
|
|
Next() (next time.Duration, stop bool)
|
|
}
|
|
|
|
var _ Backoff = (BackoffFunc)(nil)
|
|
|
|
// BackoffFunc is a backoff expressed as a function.
|
|
type BackoffFunc func() (time.Duration, bool)
|
|
|
|
// Next implements Backoff.
|
|
func (b BackoffFunc) Next() (time.Duration, bool) {
|
|
return b()
|
|
}
|
|
|
|
// WithJitter wraps a backoff function and adds the specified jitter. j can be
|
|
// interpreted as "+/- j". For example, if j were 5 seconds and the backoff
|
|
// returned 20s, the value could be between 15 and 25 seconds. The value can
|
|
// never be less than 0.
|
|
func WithJitter(j time.Duration, next Backoff) Backoff {
|
|
r := newLockedRandom(time.Now().UnixNano())
|
|
|
|
return BackoffFunc(func() (time.Duration, bool) {
|
|
val, stop := next.Next()
|
|
if stop {
|
|
return 0, true
|
|
}
|
|
|
|
diff := time.Duration(r.Int63n(int64(j)*2) - int64(j))
|
|
val = val + diff
|
|
if val < 0 {
|
|
val = 0
|
|
}
|
|
return val, false
|
|
})
|
|
}
|
|
|
|
// WithJitterPercent wraps a backoff function and adds the specified jitter
|
|
// percentage. j can be interpreted as "+/- j%". For example, if j were 5 and
|
|
// the backoff returned 20s, the value could be between 19 and 21 seconds. The
|
|
// value can never be less than 0 or greater than 100.
|
|
func WithJitterPercent(j uint64, next Backoff) Backoff {
|
|
r := newLockedRandom(time.Now().UnixNano())
|
|
|
|
return BackoffFunc(func() (time.Duration, bool) {
|
|
val, stop := next.Next()
|
|
if stop {
|
|
return 0, true
|
|
}
|
|
|
|
// Get a value between -j and j, the convert to a percentage
|
|
top := r.Int63n(int64(j)*2) - int64(j)
|
|
pct := 1 - float64(top)/100.0
|
|
|
|
val = time.Duration(float64(val) * pct)
|
|
if val < 0 {
|
|
val = 0
|
|
}
|
|
return val, false
|
|
})
|
|
}
|
|
|
|
// WithMaxRetries executes the backoff function up until the maximum attempts.
|
|
func WithMaxRetries(max uint64, next Backoff) Backoff {
|
|
var l sync.Mutex
|
|
var attempt uint64
|
|
|
|
return BackoffFunc(func() (time.Duration, bool) {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
|
|
if attempt >= max {
|
|
return 0, true
|
|
}
|
|
attempt++
|
|
|
|
val, stop := next.Next()
|
|
if stop {
|
|
return 0, true
|
|
}
|
|
|
|
return val, false
|
|
})
|
|
}
|
|
|
|
// WithCappedDuration sets a maximum on the duration returned from the next
|
|
// backoff. This is NOT a total backoff time, but rather a cap on the maximum
|
|
// value a backoff can return. Without another middleware, the backoff will
|
|
// continue infinitely.
|
|
func WithCappedDuration(cap time.Duration, next Backoff) Backoff {
|
|
return BackoffFunc(func() (time.Duration, bool) {
|
|
val, stop := next.Next()
|
|
if stop {
|
|
return 0, true
|
|
}
|
|
|
|
if val <= 0 || val > cap {
|
|
val = cap
|
|
}
|
|
return val, false
|
|
})
|
|
}
|
|
|
|
// WithMaxDuration sets a maximum on the total amount of time a backoff should
|
|
// execute. It's best-effort, and should not be used to guarantee an exact
|
|
// amount of time.
|
|
func WithMaxDuration(timeout time.Duration, next Backoff) Backoff {
|
|
start := time.Now()
|
|
|
|
return BackoffFunc(func() (time.Duration, bool) {
|
|
diff := timeout - time.Since(start)
|
|
if diff <= 0 {
|
|
return 0, true
|
|
}
|
|
|
|
val, stop := next.Next()
|
|
if stop {
|
|
return 0, true
|
|
}
|
|
|
|
if val <= 0 || val > diff {
|
|
val = diff
|
|
}
|
|
return val, false
|
|
})
|
|
}
|