generated from dogmatiq/template-go
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathstrategy.go
121 lines (101 loc) · 3.16 KB
/
strategy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package backoff
import (
"math"
"time"
"github.com/dogmatiq/linger"
)
// DefaultStrategy is the default strategy used if none is specified.
//
// It is a conservative policy favouring large delay times under the assumption
// that the operation is expensive.
var DefaultStrategy = WithTransforms(
Exponential(3*time.Second),
linger.FullJitter,
linger.Limiter(0, 1*time.Hour),
)
// Strategy is a function for computing delays between attempts to perform some
// application-defined operation.
//
// err is the error describing the operation's failure, if known. A nil error
// does not indicate a success.
//
// n is the number of successive failures since the last success, not including
// the failure indicated by err.
type Strategy func(err error, n uint) time.Duration
// Exponential returns a Strategy that uses binary exponential backoff (BEB).
//
// The unit delay is doubled after each successive failure.
func Exponential(unit time.Duration) Strategy {
if unit <= 0 {
panic("the unit duration must be positive")
}
u := float64(unit)
return func(_ error, n uint) time.Duration {
scale := math.Pow(2, float64(n))
nanos := u * scale
// Overflow check.
if nanos >= float64(linger.MaxDuration) {
return linger.MaxDuration
}
return time.Duration(nanos)
}
}
// Constant returns a Strategy that returns a fixed wait duration.
func Constant(d time.Duration) Strategy {
return func(error, uint) time.Duration {
return d
}
}
// Linear returns a Strategy that increases the wait duration linearly.
//
// The unit delay is multiplied by the number of successive failures.
func Linear(unit time.Duration) Strategy {
if unit <= 0 {
panic("the unit duration must be postive")
}
return func(_ error, n uint) time.Duration {
mult := time.Duration(n) + 1
delay := mult * unit
// Overflow check: If delay is negative, there was clearly an overflow
// because both unit and mult are positive.
if delay < 0 {
return linger.MaxDuration
}
// Overflow check: Dividing the delay by the unit value should give us
// back the multiplier that we used. If not, there was an overflow.
if delay/unit != mult {
return linger.MaxDuration
}
return delay
}
}
// WithTransforms returns a strategy that transforms the result of s using each
// of the given transforms in order.
func WithTransforms(s Strategy, transforms ...linger.DurationTransform) Strategy {
return func(err error, n uint) time.Duration {
d := s(err, n)
for _, x := range transforms {
d = x(d)
}
return d
}
}
// CoalesceStrategy returns a strategy that iterates over the given strategies
// and runs them, returning the first positive duration.
func CoalesceStrategy(strategies ...Strategy) Strategy {
return FirstStrategy(linger.Positive, strategies...)
}
// FirstStrategy returns a strategy that iterates over the given strategies
// and runs them, returning the first duration which satisfies the predicate.
// Return zero if no duration satisfies the predicate.
func FirstStrategy(p linger.DurationPredicate, strategies ...Strategy) Strategy {
return func(e error, n uint) time.Duration {
for _, s := range strategies {
d := s(e, n)
if p(d) {
return d
}
}
return 0
}
}