diff --git a/README.md b/README.md index 167e06c..7c948f6 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ b := &backoff.Backoff{ Min: 100 * time.Millisecond, Max: 10 * time.Second, Factor: 2, + Jitter: false, } fmt.Printf("%s\n", b.Duration()) @@ -72,6 +73,47 @@ for { ``` +**Exmaple using `Jitter`** + +Setting `Jitter` adds some randomization to the backoff durations. +[See amazon's writeup of performance gains using jitter](http://www.awsarchitectureblog.com/2015/03/backoff.html). +Seeding is not necessary but doing so gives repeatable results. + +```go +import "math/rand" + +b := &backoff.Backoff{ + //These are the defaults + Min: 100 * time.Millisecond, + Max: 10 * time.Second, + Factor: 2, + Jitter: true, +} + +rand.Seed(42) + +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) + +fmt.Printf("Reset!\n") +b.Reset() + +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) +fmt.Printf("%s\n", b.Duration()) +``` + +``` +100ms +106.600049ms +281.228155ms +Reset! +100ms +104.381845ms +214.957989ms +``` + #### Credits Ported from some JavaScript written by [@tj](https://github.com/tj) @@ -97,4 +139,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/backoff.go b/backoff.go index fb5d9b7..8a661eb 100644 --- a/backoff.go +++ b/backoff.go @@ -2,6 +2,7 @@ package backoff import ( "math" + "math/rand" "time" ) @@ -12,6 +13,8 @@ import ( type Backoff struct { //Factor is the multiplying factor for each increment step attempts, Factor float64 + //Jitter eases contention by randomizing backoff steps + Jitter bool //Min and Max are the minimum and maximum values of the counter Min, Max time.Duration } @@ -32,6 +35,9 @@ func (b *Backoff) Duration() time.Duration { } //calculate this duration dur := float64(b.Min) * math.Pow(b.Factor, b.attempts) + if b.Jitter == true { + dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min) + } //cap! if dur > float64(b.Max) { return b.Max diff --git a/backoff_test.go b/backoff_test.go index 27d7e23..c5fecce 100644 --- a/backoff_test.go +++ b/backoff_test.go @@ -50,6 +50,30 @@ func Test3(t *testing.T) { equals(t, b.Duration(), 100*time.Nanosecond) } +func TestJitter(t *testing.T) { + b := &Backoff{ + Min: 100 * time.Millisecond, + Max: 10 * time.Second, + Factor: 2, + Jitter: true, + } + + equals(t, b.Duration(), 100*time.Millisecond) + between(t, b.Duration(), 100*time.Millisecond, 200*time.Millisecond) + between(t, b.Duration(), 100*time.Millisecond, 400*time.Millisecond) + b.Reset() + equals(t, b.Duration(), 100*time.Millisecond) +} + +func between(t *testing.T, actual, low, high time.Duration) { + if actual < low { + t.Fatalf("Got %s, Expecting >= %s", actual, low) + } + if actual > high { + t.Fatalf("Got %s, Expecting <= %s", actual, high) + } +} + func equals(t *testing.T, d1, d2 time.Duration) { if d1 != d2 { t.Fatalf("Got %s, Expecting %s", d1, d2)