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  }