1package backoff
2
3import (
4	"math"
5	"time"
6)
7
8/*
9ExponentialBackoff implements the Backoff interface. It represents an
10instance that keeps track of retries, delays, and intervals for the
11fibonacci backoff algorithm. This struct is instantiated by
12the Exponential() function.
13*/
14type ExponentialBackoff struct {
15	Retries    int
16	MaxRetries int
17	Delay      time.Duration
18	Interval   time.Duration // time.Second, time.Millisecond, etc.
19}
20
21// Exponential creates a new instance of ExponentialBackoff.
22func Exponential() *ExponentialBackoff {
23	return &ExponentialBackoff{
24		Retries:    0,
25		MaxRetries: 5,
26		Delay:      time.Duration(0),
27		Interval:   time.Duration(1 * time.Second),
28	}
29}
30
31/*
32Next gets the next backoff delay. This method will increment the retries and check
33if the maximum number of retries has been met. If this condition is satisfied, then
34the function will return. Otherwise, the next backoff delay will be computed.
35
36The exponential backoff delay is computed as follows:
37`n = 2^c - 1` where `n` is the backoff delay and `c` is the number of retries.
38
39Example, given a 1 second interval:
40
41  Retry #        Backoff delay (in seconds)
42    0                   0
43    1                   1
44    2                   3
45    3                   7
46    4                   15
47    5                   31
48*/
49func (e *ExponentialBackoff) Next() bool {
50	if e.Retries >= e.MaxRetries {
51		return false
52	}
53
54	e.Retries++
55
56	e.Delay = time.Duration(math.Pow(2, float64(e.Retries))-1) * e.Interval
57
58	return true
59}
60
61/*
62Retry will retry a function until the maximum number of retries is met. This method expects
63the function `f` to return an error. If the failure condition is met, this method
64will surface the error outputted from `f`, otherwise nil will be returned as normal.
65*/
66func (e *ExponentialBackoff) Retry(f func() error) error {
67	err := f()
68
69	if err == nil {
70		return nil
71	}
72
73	for e.Next() {
74		if err = f(); err == nil {
75			return nil
76		}
77
78		time.Sleep(e.Delay)
79	}
80
81	return err
82}
83
84// Reset will reset the retry count and the backoff delay back to its initial state.
85func (e *ExponentialBackoff) Reset() {
86	e.Retries = 0
87	e.Delay = time.Duration(0 * time.Second)
88}
89