2019-06-06 09:58:50 +00:00
|
|
|
/*
|
|
|
|
Interesting reference on backoff:
|
|
|
|
- Exponential Backoff And Jitter (AWS Blog):
|
|
|
|
https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
|
|
|
|
|
|
We use Jitter as a default for exponential backoff, as the goal of
|
|
|
|
this module is not to provide precise 'ticks', but good behaviour to
|
|
|
|
implement retries that are helping the server to recover faster in
|
|
|
|
case of congestion.
|
|
|
|
|
|
|
|
It can be used in several ways:
|
|
|
|
- Using duration to get next sleep time.
|
|
|
|
- Using ticker channel to trigger callback function on tick
|
|
|
|
|
|
|
|
The functions for Backoff are not threadsafe, but you can:
|
2019-06-22 09:13:33 +00:00
|
|
|
- Keep the attempt counter on your end and use durationForAttempt(int)
|
2019-06-06 09:58:50 +00:00
|
|
|
- Use lock in your own code to protect the Backoff structure.
|
|
|
|
|
|
|
|
TODO: Implement Backoff Ticker channel
|
|
|
|
TODO: Implement throttler interface. Throttler could be used to implement various reconnect strategies.
|
|
|
|
*/
|
|
|
|
|
2019-06-18 14:28:30 +00:00
|
|
|
package xmpp
|
2019-06-06 09:58:50 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
"math/rand"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultBase int = 20 // Backoff base, in ms
|
|
|
|
defaultFactor int = 2
|
|
|
|
defaultCap int = 180000 // 3 minutes
|
|
|
|
)
|
|
|
|
|
2019-06-22 09:13:33 +00:00
|
|
|
// backoff provides increasing duration with the number of attempt
|
2019-06-06 09:58:50 +00:00
|
|
|
// performed. The structure is used to support exponential backoff on
|
|
|
|
// connection attempts to avoid hammering the server we are connecting
|
|
|
|
// to.
|
2019-06-22 09:13:33 +00:00
|
|
|
type backoff struct {
|
2019-06-06 09:58:50 +00:00
|
|
|
NoJitter bool
|
|
|
|
Base int
|
|
|
|
Factor int
|
|
|
|
Cap int
|
|
|
|
lastDuration int
|
|
|
|
attempt int
|
|
|
|
}
|
|
|
|
|
2019-06-22 09:13:33 +00:00
|
|
|
// duration returns the duration to apply to the current attempt.
|
|
|
|
func (b *backoff) duration() time.Duration {
|
|
|
|
d := b.durationForAttempt(b.attempt)
|
2019-06-06 09:58:50 +00:00
|
|
|
b.attempt++
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
2019-06-22 09:13:33 +00:00
|
|
|
// wait sleeps for backoff duration for current attempt.
|
|
|
|
func (b *backoff) wait() {
|
|
|
|
time.Sleep(b.duration())
|
2019-06-06 09:58:50 +00:00
|
|
|
}
|
|
|
|
|
2019-06-22 09:13:33 +00:00
|
|
|
// durationForAttempt returns a duration for an attempt number, in a stateless way.
|
|
|
|
func (b *backoff) durationForAttempt(attempt int) time.Duration {
|
2019-06-06 09:58:50 +00:00
|
|
|
b.setDefault()
|
|
|
|
expBackoff := math.Min(float64(b.Cap), float64(b.Base)*math.Pow(float64(b.Factor), float64(b.attempt)))
|
|
|
|
d := int(math.Trunc(expBackoff))
|
|
|
|
if !b.NoJitter {
|
|
|
|
d = rand.Intn(d)
|
|
|
|
}
|
|
|
|
return time.Duration(d) * time.Millisecond
|
|
|
|
}
|
|
|
|
|
2019-06-22 09:13:33 +00:00
|
|
|
// reset sets back the number of attempts to 0. This is to be called after a successful operation has been performed,
|
2019-06-06 09:58:50 +00:00
|
|
|
// to reset the exponential backoff interval.
|
2019-06-22 09:13:33 +00:00
|
|
|
func (b *backoff) reset() {
|
2019-06-06 09:58:50 +00:00
|
|
|
b.attempt = 0
|
|
|
|
}
|
|
|
|
|
2019-06-22 09:13:33 +00:00
|
|
|
func (b *backoff) setDefault() {
|
2019-06-06 09:58:50 +00:00
|
|
|
if b.Base == 0 {
|
|
|
|
b.Base = defaultBase
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.Cap == 0 {
|
|
|
|
b.Cap = defaultCap
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.Factor == 0 {
|
|
|
|
b.Factor = defaultFactor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
We use full jitter as default for now as it seems to provide good behaviour for reconnect.
|
|
|
|
|
|
|
|
Base is the default interval between attempts (if backoff Factor was equal to 1)
|
|
|
|
|
|
|
|
Attempt is the number of retry for operation. If we start attempt at 0, first sleep equals base.
|
|
|
|
|
|
|
|
Cap is the maximum sleep time duration we tolerate between attempts
|
|
|
|
*/
|