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