Skip to content

Commit

Permalink
Merge pull request #7 from achilleasa/implement-exp-backoff-helper
Browse files Browse the repository at this point in the history
#7

This PR adds an exponential back-off generator which yields parametrized delay generating functions that can be passed as `BackoffFunc` values when invoking the `Call` function.
  • Loading branch information
jujubot authored Aug 18, 2021
2 parents 9058e19 + a0f15cc commit 5526f6f
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 1 deletion.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ DoubleDelay provides a simple function that doubles the duration passed in.
This can then be easily used as the `BackoffFunc` in the `CallArgs`
structure.

## func ExpBackoff
``` go
func ExpBackoff(minDelay, maxDelay time.Duration, exp float64, applyJitter bool) func(time.Duration, int) time.Duration {
```
ExpBackoff returns a function a which generates time.Duration values using an
exponential back-off algorithm with the specified parameters. The returned value
can then be easily used as the `BackoffFunc` in the `CallArgs` structure.
The next delay value is calculated using the following formula:
`newDelay = min(minDelay * exp^attempt, maxDelay)`
If `applyJitter` is set to `true`, the function will randomly select and return
back a value in the `[minDelay, newDelay]` range.
## func IsAttemptsExceeded
``` go
Expand Down Expand Up @@ -261,4 +274,4 @@ and Clock have been specified.
- - -
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
17 changes: 17 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module github.com/juju/retry

go 1.15

require (
github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4
github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18
github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9 // indirect
github.com/juju/testing v0.0.0-20180807044555-c84dd6ba038a
github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043 // indirect
github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2 // indirect
golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4 // indirect
golang.org/x/net v0.0.0-20180406214816-61147c48b25b // indirect
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2
gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4 // indirect
gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6 // indirect
)
22 changes: 22 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4 h1:v4AMWbdtZyIX8Ohv+FEpSwaCtho9uTtGbwjZab+rDuw=
github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18 h1:Sem5Flzxj8ZdAgY2wfHBUlOYyP4wrpIfM8IZgANNGh8=
github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9 h1:Y+lzErDTURqeXqlqYi4YBYbDd7ycU74gW1ADt57/bgY=
github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20180807044555-c84dd6ba038a h1:dhnWDfRjO/h2XFBj/n3qm+wGjHm8/UqLSOk9GhZc+P0=
github.com/juju/testing v0.0.0-20180807044555-c84dd6ba038a/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043 h1:kjdsJcIYzmK2k4X2yVCi5Nip6sGoAuc7CLbp+qQnQUM=
github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2 h1:loQDi5MyxxNm7Q42mBGuPD6X+F6zw8j5S9yexLgn/BE=
github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4 h1:OfaUle5HH9Y0obNU74mlOZ/Igdtwi3eGOKcljJsTnbw=
golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180406214816-61147c48b25b h1:7rskAFQwNXGW6AD8E/6y0LDHW5mT9rsLD7ViLVFfh5w=
golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 h1:+j1SppRob9bAgoYmsdW9NNBdKZfgYuWpqnYHv78Qt8w=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4 h1:hILp2hNrRnYjZpmIbx70psAHbBSEcQ1NIzDcUbJ1b6g=
gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6 h1:CvAnnm1XvMjfib69SZzDwgWfOk+PxYz0hA0HBupilBA=
gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
33 changes: 33 additions & 0 deletions retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package retry

import (
"fmt"
"math"
"math/rand"
"time"

"github.com/juju/errors"
Expand Down Expand Up @@ -223,3 +225,34 @@ func DoubleDelay(delay time.Duration, attempt int) time.Duration {
}
return delay * 2
}

// ExpBackoff returns a function a which generates time.Duration values using
// an exponential back-off algorithm with the specified parameters. The
// returned value can then be easily used as the BackoffFunc in the CallArgs
// structure.
//
// The next delay value is calculated using the following formula:
// newDelay = min(minDelay * exp^attempt, maxDelay)
//
// If applyJitter is set to true, the function will randomly select and return
// back a value in the [minDelay, newDelay] range.
func ExpBackoff(minDelay, maxDelay time.Duration, exp float64, applyJitter bool) func(time.Duration, int) time.Duration {
minDelayF := float64(minDelay)
maxDelayF := float64(maxDelay)
return func(_ time.Duration, attempt int) time.Duration {
newDelay := minDelayF * math.Pow(exp, float64(attempt))
if newDelay > maxDelayF {
newDelay = maxDelayF
}

// Return a random value in the [minDelay, newDelay) range.
if applyJitter {
newDelay = rand.Float64() * newDelay
if newDelay < minDelayF {
newDelay = minDelayF
}
}

return time.Duration(newDelay).Round(time.Millisecond)
}
}
40 changes: 40 additions & 0 deletions retry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,43 @@ func (*retrySuite) TestMissingClockNotValid(c *gc.C) {
c.Check(err, jc.Satisfies, errors.IsNotValid)
c.Check(err, gc.ErrorMatches, `missing Clock not valid`)
}

type expBackoffSuite struct {
testing.LoggingSuite
}

var _ = gc.Suite(&expBackoffSuite{})

func (*expBackoffSuite) TestExpBackoffWithoutJitter(c *gc.C) {
backoffFunc := retry.ExpBackoff(200*time.Millisecond, 2*time.Second, 2.0, false)
expDurations := []time.Duration{
200 * time.Millisecond,
400 * time.Millisecond,
800 * time.Millisecond,
1600 * time.Millisecond,
2000 * time.Millisecond, // capped to maxDelay
}

for attempt, expDuration := range expDurations {
got := backoffFunc(0, attempt)
c.Assert(got, gc.Equals, expDuration, gc.Commentf("unexpected duration for attempt %d", attempt))
}
}

func (*expBackoffSuite) TestExpBackoffWithtJitter(c *gc.C) {
minDelay := 200 * time.Millisecond
backoffFunc := retry.ExpBackoff(minDelay, 2*time.Second, 2.0, true)
maxDurations := []time.Duration{
200 * time.Millisecond,
400 * time.Millisecond,
800 * time.Millisecond,
1600 * time.Millisecond,
2000 * time.Millisecond, // capped to maxDelay
}

for attempt, maxDuration := range maxDurations {
got := backoffFunc(0, attempt)
c.Assert(got, jc.GreaterThan, minDelay-1, gc.Commentf("expected jittered duration for attempt %d to be in the [%s, %s] range; got %s", attempt, minDelay, maxDuration, got))
c.Assert(got, jc.LessThan, maxDuration+1, gc.Commentf("expected jittered duration for attempt %d to be in the [%s, %s] range; got %s", attempt, minDelay, maxDuration, got))
}
}

0 comments on commit 5526f6f

Please sign in to comment.