-
Notifications
You must be signed in to change notification settings - Fork 1
/
valkyrietry.go
163 lines (124 loc) · 4.5 KB
/
valkyrietry.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package valkyrietry
import (
"context"
"errors"
"math/rand"
"time"
)
// RetryFunc
// Implement this function for retryable actions within the `Do` or `DoWithContext` functions.
// It is necessary to define this function if you want to utilize the
// retry mechanism inside `Do` or `DoWithContext`.
type RetryFunc func() error
// RetryFuncWithData
// Implement this function for retryable actions within the `DoWithData` or `DoWithDataAndContext` functions.
// It is necessary to define this function if you intend to use
// the retry mechanism inside `DoWithData` or `DoWithDataAndContext`.
type RetryFuncWithData[T any] func() (T, error)
type Valkyrietry struct {
Configuration *Configuration
ctx context.Context
}
func defaultValkyrietry(options ...Option) *Valkyrietry {
defaultConfiguration := &Configuration{
MaxRetryAttempts: DefaultMaxRetryAttempt,
InitialRetryDelay: DefaultRetryDelay,
RetryBackoffMultiplier: DefaultRetryBackoffMultiplier,
JitterPercentage: DefaultJitter,
}
defaultValue := &Valkyrietry{
Configuration: defaultConfiguration,
ctx: context.Background(),
}
for _, opt := range options {
opt(defaultConfiguration)
}
return defaultValue
}
func defaultValkyrietryWithContext(ctx context.Context, options ...Option) *Valkyrietry {
defaultValue := defaultValkyrietry(options...)
defaultValue.ctx = ctx
return defaultValue
}
// Do
// Start the retry mechanism using the given context and continue running the process until the `RetryFunc`
// returns successfully (without error) or until the maximum number of retry attempts is exceeded.
//
// This function ensures that the given `RetryFunc` will run at least once.
func Do(ctx context.Context, f RetryFunc, options ...Option) error {
valkyrietry := defaultValkyrietryWithContext(ctx, options...)
currentAttempt := 0
currentInterval := valkyrietry.Configuration.InitialRetryDelay
// Initialize the timer to a zero value for
// the first initialization.
timer := time.NewTimer(0)
defer func() {
timer.Stop()
}()
var err error
for {
if currentAttempt > int(valkyrietry.Configuration.MaxRetryAttempts) {
return errors.Join(err, ErrMaxRetryAttemptsExceeded)
}
err = f()
if err == nil {
return nil
}
retryInterval := valkyrietry.getRetryIntervalValue(currentInterval)
currentInterval = time.Duration(float32(currentInterval) * valkyrietry.Configuration.RetryBackoffMultiplier)
currentAttempt++
// Reset the duration to match the retry interval duration.
// Thus, we will adjust the timer interval for each retry.
timer.Reset(retryInterval)
select {
case <-valkyrietry.ctx.Done():
return valkyrietry.ctx.Err()
case <-timer.C:
}
}
}
// DoWithData
// Start the retry mechanism with any given data to receive and a context, and continue running the process until the `RetryFunc`
// successfully returns with the data (without error) or until the maximum number of retry attempts is exceeded.
//
// This function ensures that the given `RetryFunc` will run at least once.
func DoWithData[T any](ctx context.Context, f RetryFuncWithData[T], options ...Option) (T, error) {
valkyrietry := defaultValkyrietryWithContext(ctx, options...)
currentAttempt := 0
currentInterval := valkyrietry.Configuration.InitialRetryDelay
// Initialize the timer to a zero value for
// the first initialization.
timer := time.NewTimer(0)
defer func() {
timer.Stop()
}()
var response T
var err error
for {
if currentAttempt > int(valkyrietry.Configuration.MaxRetryAttempts) {
return response, errors.Join(err, ErrMaxRetryAttemptsExceeded)
}
response, err = f()
if err == nil {
return response, nil
}
retryInterval := valkyrietry.getRetryIntervalValue(currentInterval)
currentInterval = time.Duration(float32(currentInterval) * valkyrietry.Configuration.RetryBackoffMultiplier)
currentAttempt++
// Reset the duration to match the retry interval duration.
// Thus, we will adjust the timer interval for each retry.
timer.Reset(retryInterval)
select {
case <-valkyrietry.ctx.Done():
return response, valkyrietry.ctx.Err()
case <-timer.C:
}
}
}
func (v *Valkyrietry) getRetryIntervalValue(currentInterval time.Duration) time.Duration {
jitterInterval := v.Configuration.JitterPercentage * float32(currentInterval)
maxRetryInterval := float32(currentInterval) + jitterInterval
minRetryInterval := float32(currentInterval) - jitterInterval
randomValue := rand.Float32()
return time.Duration(minRetryInterval + (randomValue * (maxRetryInterval - minRetryInterval + 1)))
}