1package retry
2
3import (
4 "context"
5 "math"
6 "sync/atomic"
7 "time"
8 "unsafe"
9)
10
11type state [2]time.Duration
12
13type fibonacciBackoff struct {
14 state unsafe.Pointer
15}
16
17// Fibonacci is a wrapper around Retry that uses a Fibonacci backoff. See
18// NewFibonacci.
19func Fibonacci(ctx context.Context, base time.Duration, f RetryFunc) error {
20 return Do(ctx, NewFibonacci(base), f)
21}
22
23// NewFibonacci creates a new Fibonacci backoff using the starting value of
24// base. The wait time is the sum of the previous two wait times on each failed
25// attempt (1, 1, 2, 3, 5, 8, 13...).
26//
27// Once it overflows, the function constantly returns the maximum time.Duration
28// for a 64-bit integer.
29//
30// It panics if the given base is less than zero.
31func NewFibonacci(base time.Duration) Backoff {
32 if base <= 0 {
33 panic("base must be greater than 0")
34 }
35
36 return &fibonacciBackoff{
37 state: unsafe.Pointer(&state{0, base}),
38 }
39}
40
41// Next implements Backoff. It is safe for concurrent use.
42func (b *fibonacciBackoff) Next() (time.Duration, bool) {
43 for {
44 curr := atomic.LoadPointer(&b.state)
45 currState := (*state)(curr)
46 next := currState[0] + currState[1]
47
48 if next <= 0 {
49 return math.MaxInt64, false
50 }
51
52 if atomic.CompareAndSwapPointer(&b.state, curr, unsafe.Pointer(&state{currState[1], next})) {
53 return next, false
54 }
55 }
56}