retry.go raw
1 package dara
2
3 import (
4 "fmt"
5 "math"
6 "math/rand"
7 )
8
9 const (
10 MAX_DELAY_TIME = 120 * 1000 // 120 seconds
11 MIN_DELAY_TIME = 100 // 100 milliseconds
12 DEFAULT_MAX_CAP = 3 * 24 * 60 * 60 * 1000 // 3 days in milliseconds
13 MAX_ATTEMPTS = 3
14 )
15
16 // RetryPolicyContext holds context for the retry operation
17 type RetryPolicyContext struct {
18 Key string
19 RetriesAttempted int
20 HttpRequest *Request // placeholder for actual http.Request type
21 HttpResponse *Response // placeholder for actual http.Response type
22 Exception error
23 }
24
25 // BackoffPolicy interface with a method to get delay time
26 type BackoffPolicy interface {
27 GetDelayTime(ctx *RetryPolicyContext) int
28 }
29
30 // BackoffPolicyFactory creates a BackoffPolicy based on the option
31 func BackoffPolicyFactory(option map[string]interface{}) (BackoffPolicy, error) {
32
33 switch option["policy"] {
34 case "Fixed":
35 return NewFixedBackoffPolicy(option), nil
36 case "Random":
37 return NewRandomBackoffPolicy(option), nil
38 case "Exponential":
39 return NewExponentialBackoffPolicy(option), nil
40 case "EqualJitter", "ExponentialWithEqualJitter":
41 return NewEqualJitterBackoffPolicy(option), nil
42 case "FullJitter", "ExponentialWithFullJitter":
43 return NewFullJitterBackoffPolicy(option), nil
44 }
45 return nil, fmt.Errorf("unknown policy type")
46 }
47
48 // FixedBackoffPolicy implementation
49 type FixedBackoffPolicy struct {
50 Period int
51 }
52
53 func NewFixedBackoffPolicy(option map[string]interface{}) *FixedBackoffPolicy {
54 var period int
55 if v, ok := option["period"]; ok {
56 period = v.(int)
57 }
58 return &FixedBackoffPolicy{
59 Period: period,
60 }
61 }
62
63 func (f *FixedBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
64 return f.Period
65 }
66
67 // RandomBackoffPolicy implementation
68 type RandomBackoffPolicy struct {
69 Period int
70 Cap int
71 }
72
73 func NewRandomBackoffPolicy(option map[string]interface{}) *RandomBackoffPolicy {
74 var capValue int
75 var period int
76 if v, ok := option["cap"]; ok {
77 capValue = v.(int)
78 } else {
79 capValue = 20 * 1000
80 }
81 if v, ok := option["period"]; ok {
82 period = v.(int)
83 }
84 return &RandomBackoffPolicy{
85 Period: period,
86 Cap: capValue,
87 }
88 }
89
90 func (r *RandomBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
91 randomSeed := int64(ctx.RetriesAttempted * r.Period)
92 randomTime := int(rand.Int63n(randomSeed))
93 if randomTime > r.Cap {
94 return r.Cap
95 }
96 return randomTime
97 }
98
99 // ExponentialBackoffPolicy implementation
100 type ExponentialBackoffPolicy struct {
101 Period int
102 Cap int
103 }
104
105 func NewExponentialBackoffPolicy(option map[string]interface{}) *ExponentialBackoffPolicy {
106 var capValue int
107 var period int
108 if v, ok := option["cap"]; ok {
109 capValue = v.(int)
110 } else {
111 capValue = DEFAULT_MAX_CAP
112 }
113 if v, ok := option["period"]; ok {
114 period = v.(int)
115 }
116 return &ExponentialBackoffPolicy{
117 Period: period,
118 Cap: capValue,
119 }
120 }
121
122 func (e *ExponentialBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
123 randomTime := int(math.Pow(2, float64(ctx.RetriesAttempted)*float64(e.Period)))
124 if randomTime > e.Cap {
125 return e.Cap
126 }
127 return randomTime
128 }
129
130 // EqualJitterBackoffPolicy implementation
131 type EqualJitterBackoffPolicy struct {
132 Period int
133 Cap int
134 }
135
136 func NewEqualJitterBackoffPolicy(option map[string]interface{}) *EqualJitterBackoffPolicy {
137 var capValue int
138 var period int
139 if v, ok := option["cap"]; ok {
140 capValue = v.(int)
141 } else {
142 capValue = DEFAULT_MAX_CAP
143 }
144
145 if v, ok := option["period"]; ok {
146 period = v.(int)
147 }
148 return &EqualJitterBackoffPolicy{
149 Period: period,
150 Cap: capValue,
151 }
152 }
153
154 func (e *EqualJitterBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
155 ceil := int64(math.Min(float64(e.Cap), float64(math.Pow(2, float64(ctx.RetriesAttempted)*float64(e.Period)))))
156 randNum := rand.Int63n(ceil/2 + 1)
157 return int(ceil/2 + randNum)
158 }
159
160 // FullJitterBackoffPolicy implementation
161 type FullJitterBackoffPolicy struct {
162 Period int
163 Cap int
164 }
165
166 func NewFullJitterBackoffPolicy(option map[string]interface{}) *FullJitterBackoffPolicy {
167 var capValue int
168 var period int
169 if v, ok := option["cap"]; ok {
170 capValue = v.(int)
171 } else {
172 capValue = DEFAULT_MAX_CAP
173 }
174 if v, ok := option["period"]; ok {
175 period = v.(int)
176 }
177 return &FullJitterBackoffPolicy{
178 Period: period,
179 Cap: capValue,
180 }
181 }
182
183 func (f *FullJitterBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
184 ceil := int64(math.Min(float64(f.Cap), float64(math.Pow(2, float64(ctx.RetriesAttempted)*float64(f.Period)))))
185 return int(rand.Int63n(ceil))
186 }
187
188 // RetryCondition holds the retry conditions
189 type RetryCondition struct {
190 MaxAttempts int
191 Backoff BackoffPolicy
192 Exception []string
193 ErrorCode []string
194 MaxDelay int
195 }
196
197 func NewRetryCondition(condition map[string]interface{}) *RetryCondition {
198 var backoff BackoffPolicy
199 if condition["backoff"] != nil {
200 backoffOption := condition["backoff"].(map[string]interface{})
201 backoff, _ = BackoffPolicyFactory(backoffOption)
202 }
203 maxAttempts, ok := condition["maxAttempts"].(int)
204 if !ok {
205 maxAttempts = MAX_ATTEMPTS
206 }
207
208 exception, ok := condition["exception"].([]string)
209 if !ok {
210 exception = []string{}
211 }
212
213 errorCode, ok := condition["errorCode"].([]string)
214 if !ok {
215 errorCode = []string{}
216 }
217
218 maxDelay, ok := condition["maxDelay"].(int)
219 if !ok {
220 maxDelay = MAX_DELAY_TIME
221 }
222
223 return &RetryCondition{
224 MaxAttempts: maxAttempts,
225 Backoff: backoff,
226 Exception: exception,
227 ErrorCode: errorCode,
228 MaxDelay: maxDelay,
229 }
230 }
231
232 // RetryOptions holds the retry options
233 type RetryOptions struct {
234 Retryable bool
235 RetryCondition []*RetryCondition
236 NoRetryCondition []*RetryCondition
237 }
238
239 func NewRetryOptions(options map[string]interface{}) *RetryOptions {
240 retryConditions := make([]*RetryCondition, 0)
241 for _, cond := range options["retryCondition"].([]interface{}) {
242 condition := NewRetryCondition(cond.(map[string]interface{}))
243 retryConditions = append(retryConditions, condition)
244 }
245
246 noRetryConditions := make([]*RetryCondition, 0)
247 for _, cond := range options["noRetryCondition"].([]interface{}) {
248 condition := NewRetryCondition(cond.(map[string]interface{}))
249 noRetryConditions = append(noRetryConditions, condition)
250 }
251
252 return &RetryOptions{
253 Retryable: options["retryable"].(bool),
254 RetryCondition: retryConditions,
255 NoRetryCondition: noRetryConditions,
256 }
257 }
258
259 // shouldRetry determines if a retry should be attempted
260 func ShouldRetry(options *RetryOptions, ctx *RetryPolicyContext) bool {
261 if ctx.RetriesAttempted == 0 {
262 return true
263 }
264
265 if options == nil || !options.Retryable {
266 return false
267 }
268
269 retriesAttempted := ctx.RetriesAttempted
270 ex := ctx.Exception
271 if baseErr, ok := ex.(BaseError); ok {
272 conditions := options.NoRetryCondition
273
274 for _, condition := range conditions {
275 for _, exc := range condition.Exception {
276 if exc == StringValue(baseErr.GetName()) {
277 return false
278 }
279 }
280 for _, code := range condition.ErrorCode {
281 if code == StringValue(baseErr.GetCode()) {
282 return false
283 }
284 }
285 }
286
287 conditions = options.RetryCondition
288 for _, condition := range conditions {
289 for _, exc := range condition.Exception {
290 if exc == StringValue(baseErr.GetName()) {
291 if retriesAttempted >= condition.MaxAttempts {
292 return false
293 }
294 return true
295 }
296 }
297 for _, code := range condition.ErrorCode {
298 if code == StringValue(baseErr.GetCode()) {
299 if retriesAttempted >= condition.MaxAttempts {
300 return false
301 }
302 return true
303 }
304 }
305 }
306 }
307
308 return false
309 }
310
311 // getBackoffDelay calculates backoff delay
312 func GetBackoffDelay(options *RetryOptions, ctx *RetryPolicyContext) int {
313 if ctx.RetriesAttempted == 0 {
314 return 0
315 }
316
317 if options == nil || !options.Retryable {
318 return MIN_DELAY_TIME
319 }
320
321 ex := ctx.Exception
322 conditions := options.RetryCondition
323 if baseErr, ok := ex.(BaseError); ok {
324 for _, condition := range conditions {
325 for _, exc := range condition.Exception {
326 if exc == StringValue(baseErr.GetName()) {
327 maxDelay := condition.MaxDelay
328 // Simulated "retryAfter" from an error response
329 if respErr, ok := ex.(ResponseError); ok {
330 retryAfter := Int64Value(respErr.GetRetryAfter())
331 if retryAfter != 0 {
332 return min(int(retryAfter), maxDelay)
333 }
334 }
335 // This would be set properly based on your error handling
336
337 if condition.Backoff == nil {
338 return MIN_DELAY_TIME
339 }
340 return min(condition.Backoff.GetDelayTime(ctx), maxDelay)
341 }
342 }
343
344 for _, code := range condition.ErrorCode {
345 if code == StringValue(baseErr.GetCode()) {
346 maxDelay := condition.MaxDelay
347 // Simulated "retryAfter" from an error response
348 if respErr, ok := ex.(ResponseError); ok {
349 retryAfter := Int64Value(respErr.GetRetryAfter())
350 if retryAfter != 0 {
351 return min(int(retryAfter), maxDelay)
352 }
353 }
354
355 if condition.Backoff == nil {
356 return MIN_DELAY_TIME
357 }
358
359 return min(condition.Backoff.GetDelayTime(ctx), maxDelay)
360 }
361 }
362 }
363 }
364 return MIN_DELAY_TIME
365 }
366
367 // helper function to find the minimum of two values
368 func min(a, b int) int {
369 if a < b {
370 return a
371 }
372 return b
373 }
374