github.com/alibabacloud-go/tea@v1.3.10/dara/retry.go (about) 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 }