backoff_fibonacci.go

 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}