123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- package dara
- import (
- "fmt"
- "math"
- "math/rand"
- )
- const (
- MAX_DELAY_TIME = 120 * 1000 // 120 seconds
- MIN_DELAY_TIME = 100 // 100 milliseconds
- DEFAULT_MAX_CAP = 3 * 24 * 60 * 60 * 1000 // 3 days in milliseconds
- MAX_ATTEMPTS = 3
- )
- // RetryPolicyContext holds context for the retry operation
- type RetryPolicyContext struct {
- Key string
- RetriesAttempted int
- HttpRequest *Request // placeholder for actual http.Request type
- HttpResponse *Response // placeholder for actual http.Response type
- Exception error
- }
- // BackoffPolicy interface with a method to get delay time
- type BackoffPolicy interface {
- GetDelayTime(ctx *RetryPolicyContext) int
- }
- // BackoffPolicyFactory creates a BackoffPolicy based on the option
- func BackoffPolicyFactory(option map[string]interface{}) (BackoffPolicy, error) {
- switch option["policy"] {
- case "Fixed":
- return NewFixedBackoffPolicy(option), nil
- case "Random":
- return NewRandomBackoffPolicy(option), nil
- case "Exponential":
- return NewExponentialBackoffPolicy(option), nil
- case "EqualJitter", "ExponentialWithEqualJitter":
- return NewEqualJitterBackoffPolicy(option), nil
- case "FullJitter", "ExponentialWithFullJitter":
- return NewFullJitterBackoffPolicy(option), nil
- }
- return nil, fmt.Errorf("unknown policy type")
- }
- // FixedBackoffPolicy implementation
- type FixedBackoffPolicy struct {
- Period int
- }
- func NewFixedBackoffPolicy(option map[string]interface{}) *FixedBackoffPolicy {
- var period int
- if v, ok := option["period"]; ok {
- period = v.(int)
- }
- return &FixedBackoffPolicy{
- Period: period,
- }
- }
- func (f *FixedBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
- return f.Period
- }
- // RandomBackoffPolicy implementation
- type RandomBackoffPolicy struct {
- Period int
- Cap int
- }
- func NewRandomBackoffPolicy(option map[string]interface{}) *RandomBackoffPolicy {
- var capValue int
- var period int
- if v, ok := option["cap"]; ok {
- capValue = v.(int)
- } else {
- capValue = 20 * 1000
- }
- if v, ok := option["period"]; ok {
- period = v.(int)
- }
- return &RandomBackoffPolicy{
- Period: period,
- Cap: capValue,
- }
- }
- func (r *RandomBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
- randomSeed := int64(ctx.RetriesAttempted * r.Period)
- randomTime := int(rand.Int63n(randomSeed))
- if randomTime > r.Cap {
- return r.Cap
- }
- return randomTime
- }
- // ExponentialBackoffPolicy implementation
- type ExponentialBackoffPolicy struct {
- Period int
- Cap int
- }
- func NewExponentialBackoffPolicy(option map[string]interface{}) *ExponentialBackoffPolicy {
- var capValue int
- var period int
- if v, ok := option["cap"]; ok {
- capValue = v.(int)
- } else {
- capValue = DEFAULT_MAX_CAP
- }
- if v, ok := option["period"]; ok {
- period = v.(int)
- }
- return &ExponentialBackoffPolicy{
- Period: period,
- Cap: capValue,
- }
- }
- func (e *ExponentialBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
- randomTime := int(math.Pow(2, float64(ctx.RetriesAttempted)*float64(e.Period)))
- if randomTime > e.Cap {
- return e.Cap
- }
- return randomTime
- }
- // EqualJitterBackoffPolicy implementation
- type EqualJitterBackoffPolicy struct {
- Period int
- Cap int
- }
- func NewEqualJitterBackoffPolicy(option map[string]interface{}) *EqualJitterBackoffPolicy {
- var capValue int
- var period int
- if v, ok := option["cap"]; ok {
- capValue = v.(int)
- } else {
- capValue = DEFAULT_MAX_CAP
- }
- if v, ok := option["period"]; ok {
- period = v.(int)
- }
- return &EqualJitterBackoffPolicy{
- Period: period,
- Cap: capValue,
- }
- }
- func (e *EqualJitterBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
- ceil := int64(math.Min(float64(e.Cap), float64(math.Pow(2, float64(ctx.RetriesAttempted)*float64(e.Period)))))
- randNum := rand.Int63n(ceil/2 + 1)
- return int(ceil/2 + randNum)
- }
- // FullJitterBackoffPolicy implementation
- type FullJitterBackoffPolicy struct {
- Period int
- Cap int
- }
- func NewFullJitterBackoffPolicy(option map[string]interface{}) *FullJitterBackoffPolicy {
- var capValue int
- var period int
- if v, ok := option["cap"]; ok {
- capValue = v.(int)
- } else {
- capValue = DEFAULT_MAX_CAP
- }
- if v, ok := option["period"]; ok {
- period = v.(int)
- }
- return &FullJitterBackoffPolicy{
- Period: period,
- Cap: capValue,
- }
- }
- func (f *FullJitterBackoffPolicy) GetDelayTime(ctx *RetryPolicyContext) int {
- ceil := int64(math.Min(float64(f.Cap), float64(math.Pow(2, float64(ctx.RetriesAttempted)*float64(f.Period)))))
- return int(rand.Int63n(ceil))
- }
- // RetryCondition holds the retry conditions
- type RetryCondition struct {
- MaxAttempts int
- Backoff BackoffPolicy
- Exception []string
- ErrorCode []string
- MaxDelay int
- }
- func NewRetryCondition(condition map[string]interface{}) *RetryCondition {
- var backoff BackoffPolicy
- if condition["backoff"] != nil {
- backoffOption := condition["backoff"].(map[string]interface{})
- backoff, _ = BackoffPolicyFactory(backoffOption)
- }
- maxAttempts, ok := condition["maxAttempts"].(int)
- if !ok {
- maxAttempts = MAX_ATTEMPTS
- }
- exception, ok := condition["exception"].([]string)
- if !ok {
- exception = []string{}
- }
- errorCode, ok := condition["errorCode"].([]string)
- if !ok {
- errorCode = []string{}
- }
- maxDelay, ok := condition["maxDelay"].(int)
- if !ok {
- maxDelay = MAX_DELAY_TIME
- }
- return &RetryCondition{
- MaxAttempts: maxAttempts,
- Backoff: backoff,
- Exception: exception,
- ErrorCode: errorCode,
- MaxDelay: maxDelay,
- }
- }
- // RetryOptions holds the retry options
- type RetryOptions struct {
- Retryable bool
- RetryCondition []*RetryCondition
- NoRetryCondition []*RetryCondition
- }
- func NewRetryOptions(options map[string]interface{}) *RetryOptions {
- retryConditions := make([]*RetryCondition, 0)
- for _, cond := range options["retryCondition"].([]interface{}) {
- condition := NewRetryCondition(cond.(map[string]interface{}))
- retryConditions = append(retryConditions, condition)
- }
- noRetryConditions := make([]*RetryCondition, 0)
- for _, cond := range options["noRetryCondition"].([]interface{}) {
- condition := NewRetryCondition(cond.(map[string]interface{}))
- noRetryConditions = append(noRetryConditions, condition)
- }
- return &RetryOptions{
- Retryable: options["retryable"].(bool),
- RetryCondition: retryConditions,
- NoRetryCondition: noRetryConditions,
- }
- }
- // shouldRetry determines if a retry should be attempted
- func ShouldRetry(options *RetryOptions, ctx *RetryPolicyContext) bool {
- if ctx.RetriesAttempted == 0 {
- return true
- }
- if options == nil || !options.Retryable {
- return false
- }
- retriesAttempted := ctx.RetriesAttempted
- ex := ctx.Exception
- if baseErr, ok := ex.(BaseError); ok {
- conditions := options.NoRetryCondition
- for _, condition := range conditions {
- for _, exc := range condition.Exception {
- if exc == StringValue(baseErr.GetName()) {
- return false
- }
- }
- for _, code := range condition.ErrorCode {
- if code == StringValue(baseErr.GetCode()) {
- return false
- }
- }
- }
- conditions = options.RetryCondition
- for _, condition := range conditions {
- for _, exc := range condition.Exception {
- if exc == StringValue(baseErr.GetName()) {
- if retriesAttempted >= condition.MaxAttempts {
- return false
- }
- return true
- }
- }
- for _, code := range condition.ErrorCode {
- if code == StringValue(baseErr.GetCode()) {
- if retriesAttempted >= condition.MaxAttempts {
- return false
- }
- return true
- }
- }
- }
- }
- return false
- }
- // getBackoffDelay calculates backoff delay
- func GetBackoffDelay(options *RetryOptions, ctx *RetryPolicyContext) int {
- if ctx.RetriesAttempted == 0 {
- return 0
- }
- if options == nil || !options.Retryable {
- return MIN_DELAY_TIME
- }
- ex := ctx.Exception
- conditions := options.RetryCondition
- if baseErr, ok := ex.(BaseError); ok {
- for _, condition := range conditions {
- for _, exc := range condition.Exception {
- if exc == StringValue(baseErr.GetName()) {
- maxDelay := condition.MaxDelay
- // Simulated "retryAfter" from an error response
- if respErr, ok := ex.(ResponseError); ok {
- retryAfter := Int64Value(respErr.GetRetryAfter())
- if retryAfter != 0 {
- return min(int(retryAfter), maxDelay)
- }
- }
- // This would be set properly based on your error handling
- if condition.Backoff == nil {
- return MIN_DELAY_TIME
- }
- return min(condition.Backoff.GetDelayTime(ctx), maxDelay)
- }
- }
- for _, code := range condition.ErrorCode {
- if code == StringValue(baseErr.GetCode()) {
- maxDelay := condition.MaxDelay
- // Simulated "retryAfter" from an error response
- if respErr, ok := ex.(ResponseError); ok {
- retryAfter := Int64Value(respErr.GetRetryAfter())
- if retryAfter != 0 {
- return min(int(retryAfter), maxDelay)
- }
- }
- if condition.Backoff == nil {
- return MIN_DELAY_TIME
- }
- return min(condition.Backoff.GetDelayTime(ctx), maxDelay)
- }
- }
- }
- }
- return MIN_DELAY_TIME
- }
- // helper function to find the minimum of two values
- func min(a, b int) int {
- if a < b {
- return a
- }
- return b
- }
|