forked from jshiffer/go-xmpp
2f391fde80
- Support for exponential backoff on reconnect to be gentle on the server. - Clean up client by moving metrics and retry strategy to the connection manager. - Update echo_client to use client manager - Fix echo client XMPP message matching Fixes #21 Improvements for #8
102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
/*
|
|
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:
|
|
- Keep the attempt counter on your end and use DurationForAttempt(int)
|
|
- 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.
|
|
*/
|
|
|
|
package xmpp // import "gosrc.io/xmpp"
|
|
|
|
import (
|
|
"math"
|
|
"math/rand"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultBase int = 20 // Backoff base, in ms
|
|
defaultFactor int = 2
|
|
defaultCap int = 180000 // 3 minutes
|
|
)
|
|
|
|
// Backoff can provide increasing duration with the number of attempt
|
|
// performed. The structure is used to support exponential backoff on
|
|
// connection attempts to avoid hammering the server we are connecting
|
|
// to.
|
|
type Backoff struct {
|
|
NoJitter bool
|
|
Base int
|
|
Factor int
|
|
Cap int
|
|
lastDuration int
|
|
attempt int
|
|
}
|
|
|
|
// Duration returns the duration to apply to the current attempt.
|
|
func (b *Backoff) Duration() time.Duration {
|
|
d := b.DurationForAttempt(b.attempt)
|
|
b.attempt++
|
|
return d
|
|
}
|
|
|
|
// Wait sleeps for backoff duration for current attempt.
|
|
func (b *Backoff) Wait() {
|
|
time.Sleep(b.Duration())
|
|
}
|
|
|
|
// DurationForAttempt returns a duration for an attempt number, in a stateless way.
|
|
func (b *Backoff) DurationForAttempt(attempt int) time.Duration {
|
|
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
|
|
}
|
|
|
|
// Reset sets back the number of attempts to 0. This is to be called after a successfull operation has been performed,
|
|
// to reset the exponential backoff interval.
|
|
func (b *Backoff) Reset() {
|
|
b.attempt = 0
|
|
}
|
|
|
|
func (b *Backoff) setDefault() {
|
|
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
|
|
*/
|