github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/engines/kafka/thirdparty/retry.go (about)

     1  /*
     2  Copyright 2021 The Dapr Authors
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  See the License for the specific language governing permissions and
    11  limitations under the License.
    12  */
    13  
    14  package thirdparty
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"reflect"
    20  	"strconv"
    21  	"strings"
    22  	"sync/atomic"
    23  	"time"
    24  	"unicode"
    25  
    26  	"github.com/mitchellh/mapstructure"
    27  
    28  	"github.com/cenkalti/backoff/v4"
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  // PolicyType denotes if the back off delay should be constant or exponential.
    33  type PolicyType int
    34  
    35  const (
    36  	// PolicyConstant is a backoff policy that always returns the same backoff delay.
    37  	PolicyConstant PolicyType = iota
    38  	// PolicyExponential is a backoff implementation that increases the backoff period
    39  	// for each retry attempt using a randomization function that grows exponentially.
    40  	PolicyExponential
    41  )
    42  
    43  // Config encapsulates the back off policy configuration.
    44  type Config struct {
    45  	Policy PolicyType `mapstructure:"policy"`
    46  
    47  	// Constant back off
    48  	Duration time.Duration `mapstructure:"duration"`
    49  
    50  	// Exponential back off
    51  	InitialInterval     time.Duration `mapstructure:"initialInterval"`
    52  	RandomizationFactor float32       `mapstructure:"randomizationFactor"`
    53  	Multiplier          float32       `mapstructure:"multiplier"`
    54  	MaxInterval         time.Duration `mapstructure:"maxInterval"`
    55  	MaxElapsedTime      time.Duration `mapstructure:"maxElapsedTime"`
    56  
    57  	// Additional options
    58  	MaxRetries int64 `mapstructure:"maxRetries"`
    59  }
    60  
    61  // String implements fmt.Stringer and is used for debugging.
    62  func (c Config) String() string {
    63  	return fmt.Sprintf(
    64  		"policy='%s' duration='%v' initialInterval='%v' randomizationFactor='%f' multiplier='%f' maxInterval='%v' maxElapsedTime='%v' maxRetries='%d'",
    65  		c.Policy, c.Duration, c.InitialInterval, c.RandomizationFactor, c.Multiplier, c.MaxInterval, c.MaxElapsedTime, c.MaxRetries,
    66  	)
    67  }
    68  
    69  // DefaultConfig represents the default configuration for a `Config`.
    70  func DefaultConfig() Config {
    71  	return Config{
    72  		Policy:              PolicyConstant,
    73  		Duration:            5 * time.Second,
    74  		InitialInterval:     backoff.DefaultInitialInterval,
    75  		RandomizationFactor: backoff.DefaultRandomizationFactor,
    76  		Multiplier:          backoff.DefaultMultiplier,
    77  		MaxInterval:         backoff.DefaultMaxInterval,
    78  		MaxElapsedTime:      backoff.DefaultMaxElapsedTime,
    79  		MaxRetries:          -1,
    80  	}
    81  }
    82  
    83  // NewBackOff returns a BackOff instance for use with `NotifyRecover`
    84  // or `backoff.RetryNotify` directly. The instance will not stop due to
    85  // context cancellation. To support cancellation (recommended), use
    86  // `NewBackOffWithContext`.
    87  //
    88  // Since the underlying backoff implementations are not always thread safe,
    89  // `NewBackOff` or `NewBackOffWithContext` should be called each time
    90  // `RetryNotifyRecover` or `backoff.RetryNotify` is used.
    91  func (c *Config) NewBackOff() backoff.BackOff {
    92  	var b backoff.BackOff
    93  	switch c.Policy {
    94  	case PolicyConstant:
    95  		b = backoff.NewConstantBackOff(c.Duration)
    96  	case PolicyExponential:
    97  		eb := backoff.NewExponentialBackOff()
    98  		eb.InitialInterval = c.InitialInterval
    99  		eb.RandomizationFactor = float64(c.RandomizationFactor)
   100  		eb.Multiplier = float64(c.Multiplier)
   101  		eb.MaxInterval = c.MaxInterval
   102  		eb.MaxElapsedTime = c.MaxElapsedTime
   103  		b = eb
   104  	}
   105  
   106  	if c.MaxRetries >= 0 {
   107  		b = backoff.WithMaxRetries(b, uint64(c.MaxRetries))
   108  	}
   109  
   110  	return b
   111  }
   112  
   113  // NewBackOffWithContext returns a BackOff instance for use with `RetryNotifyRecover`
   114  // or `backoff.RetryNotify` directly. The provided context is used to cancel retries
   115  // if it is canceled.
   116  //
   117  // Since the underlying backoff implementations are not always thread safe,
   118  // `NewBackOff` or `NewBackOffWithContext` should be called each time
   119  // `RetryNotifyRecover` or `backoff.RetryNotify` is used.
   120  func (c *Config) NewBackOffWithContext(ctx context.Context) backoff.BackOff {
   121  	b := c.NewBackOff()
   122  
   123  	return backoff.WithContext(b, ctx)
   124  }
   125  
   126  // DecodeConfigWithPrefix decodes a Go struct into a `Config`.
   127  func DecodeConfigWithPrefix(c *Config, input interface{}, prefix string) error {
   128  	input, err := PrefixedBy(input, prefix)
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	return DecodeConfig(c, input)
   134  }
   135  
   136  // DecodeConfig decodes a Go struct into a `Config`.
   137  func DecodeConfig(c *Config, input interface{}) error {
   138  	// Use the deefault config if `c` is empty/zero value.
   139  	var emptyConfig Config
   140  	if *c == emptyConfig {
   141  		*c = DefaultConfig()
   142  	}
   143  
   144  	return Decode(input, c)
   145  }
   146  func Decode(input interface{}, output interface{}) error {
   147  	decoder, err := mapstructure.NewDecoder(
   148  		&mapstructure.DecoderConfig{ //nolint: exhaustruct
   149  			Result:     output,
   150  			DecodeHook: decodeString,
   151  		})
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	return decoder.Decode(input)
   157  }
   158  
   159  var (
   160  	typeDuration      = reflect.TypeOf(time.Duration(5))             //nolint: gochecknoglobals
   161  	typeTime          = reflect.TypeOf(time.Time{})                  //nolint: gochecknoglobals
   162  	typeStringDecoder = reflect.TypeOf((*StringDecoder)(nil)).Elem() //nolint: gochecknoglobals
   163  )
   164  
   165  type StringDecoder interface {
   166  	DecodeString(value string) error
   167  }
   168  
   169  //nolint:cyclop
   170  func decodeString(f reflect.Type, t reflect.Type, data any) (any, error) {
   171  	if t.Kind() == reflect.String && f.Kind() != reflect.String {
   172  		return fmt.Sprintf("%v", data), nil
   173  	}
   174  	if f.Kind() == reflect.Ptr {
   175  		f = f.Elem()
   176  		data = reflect.ValueOf(data).Elem().Interface()
   177  	}
   178  	if f.Kind() != reflect.String {
   179  		return data, nil
   180  	}
   181  
   182  	dataString, ok := data.(string)
   183  	if !ok {
   184  		return nil, errors.Errorf("expected string: got %s", reflect.TypeOf(data))
   185  	}
   186  
   187  	var result any
   188  	var decoder StringDecoder
   189  
   190  	if t.Implements(typeStringDecoder) {
   191  		result = reflect.New(t.Elem()).Interface()
   192  		decoder = result.(StringDecoder)
   193  	} else if reflect.PtrTo(t).Implements(typeStringDecoder) {
   194  		result = reflect.New(t).Interface()
   195  		decoder = result.(StringDecoder)
   196  	}
   197  
   198  	if decoder != nil {
   199  		if err := decoder.DecodeString(dataString); err != nil {
   200  			if t.Kind() == reflect.Ptr {
   201  				t = t.Elem()
   202  			}
   203  
   204  			return nil, errors.Errorf("invalid %s %q: %v", t.Name(), dataString, err)
   205  		}
   206  
   207  		return result, nil
   208  	}
   209  
   210  	switch t {
   211  	case typeDuration:
   212  		// Check for simple integer values and treat them
   213  		// as milliseconds
   214  		if val, err := strconv.Atoi(dataString); err == nil {
   215  			return time.Duration(val) * time.Millisecond, nil
   216  		}
   217  
   218  		// Convert it by parsing
   219  		d, err := time.ParseDuration(dataString)
   220  
   221  		return d, invalidError(err, "duration", dataString)
   222  	case typeTime:
   223  		// Convert it by parsing
   224  		t, err := time.Parse(time.RFC3339Nano, dataString)
   225  		if err == nil {
   226  			return t, nil
   227  		}
   228  		t, err = time.Parse(time.RFC3339, dataString)
   229  
   230  		return t, invalidError(err, "time", dataString)
   231  	}
   232  
   233  	switch t.Kind() {
   234  	case reflect.Uint:
   235  		val, err := strconv.ParseUint(dataString, 10, 32)
   236  
   237  		return uint(val), invalidError(err, "uint", dataString)
   238  	case reflect.Uint64:
   239  		val, err := strconv.ParseUint(dataString, 10, 64)
   240  
   241  		return val, invalidError(err, "uint64", dataString)
   242  	case reflect.Uint32:
   243  		val, err := strconv.ParseUint(dataString, 10, 32)
   244  
   245  		return uint32(val), invalidError(err, "uint32", dataString)
   246  	case reflect.Uint16:
   247  		val, err := strconv.ParseUint(dataString, 10, 16)
   248  
   249  		return uint16(val), invalidError(err, "uint16", dataString)
   250  	case reflect.Uint8:
   251  		val, err := strconv.ParseUint(dataString, 10, 8)
   252  
   253  		return uint8(val), invalidError(err, "uint8", dataString)
   254  
   255  	case reflect.Int:
   256  		val, err := strconv.Atoi(dataString)
   257  
   258  		return val, invalidError(err, "int", dataString)
   259  	case reflect.Int64:
   260  		val, err := strconv.ParseInt(dataString, 10, 64)
   261  
   262  		return val, invalidError(err, "int64", dataString)
   263  	case reflect.Int32:
   264  		val, err := strconv.ParseInt(dataString, 10, 32)
   265  
   266  		return int32(val), invalidError(err, "int32", dataString)
   267  	case reflect.Int16:
   268  		val, err := strconv.ParseInt(dataString, 10, 16)
   269  
   270  		return int16(val), invalidError(err, "int16", dataString)
   271  	case reflect.Int8:
   272  		val, err := strconv.ParseInt(dataString, 10, 8)
   273  
   274  		return int8(val), invalidError(err, "int8", dataString)
   275  
   276  	case reflect.Float32:
   277  		val, err := strconv.ParseFloat(dataString, 32)
   278  
   279  		return float32(val), invalidError(err, "float32", dataString)
   280  	case reflect.Float64:
   281  		val, err := strconv.ParseFloat(dataString, 64)
   282  
   283  		return val, invalidError(err, "float64", dataString)
   284  
   285  	case reflect.Bool:
   286  		val, err := strconv.ParseBool(dataString)
   287  
   288  		return val, invalidError(err, "bool", dataString)
   289  
   290  	default:
   291  		return data, nil
   292  	}
   293  }
   294  func invalidError(err error, msg, value string) error {
   295  	if err == nil {
   296  		return nil
   297  	}
   298  
   299  	return errors.Errorf("invalid %s %q", msg, value)
   300  }
   301  
   302  // NotifyRecover is a wrapper around backoff.RetryNotify that adds another callback for when an operation
   303  // previously failed but has since recovered. The main purpose of this wrapper is to call `notify` only when
   304  // the operations fails the first time and `recovered` when it finally succeeds. This can be helpful in limiting
   305  // log messages to only the events that operators need to be alerted on.
   306  func NotifyRecover(operation backoff.Operation, b backoff.BackOff, notify backoff.Notify, recovered func()) error {
   307  	notified := atomic.Bool{}
   308  
   309  	return backoff.RetryNotify(func() error {
   310  		err := operation()
   311  
   312  		if err == nil && notified.CompareAndSwap(true, false) {
   313  			recovered()
   314  		}
   315  
   316  		return err
   317  	}, b, func(err error, d time.Duration) {
   318  		if notified.CompareAndSwap(false, true) {
   319  			notify(err, d)
   320  		}
   321  	})
   322  }
   323  
   324  // NotifyRecoverWithData is a variant of NotifyRecover that also returns data in addition to an error.
   325  func NotifyRecoverWithData[T any](operation backoff.OperationWithData[T], b backoff.BackOff, notify backoff.Notify, recovered func()) (T, error) {
   326  	notified := atomic.Bool{}
   327  
   328  	return backoff.RetryNotifyWithData(func() (T, error) {
   329  		res, err := operation()
   330  
   331  		if err == nil && notified.CompareAndSwap(true, false) {
   332  			recovered()
   333  		}
   334  
   335  		return res, err
   336  	}, b, func(err error, d time.Duration) {
   337  		if notified.CompareAndSwap(false, true) {
   338  			notify(err, d)
   339  		}
   340  	})
   341  }
   342  
   343  // DecodeString handles converting a string value to `p`.
   344  func (p *PolicyType) DecodeString(value string) error {
   345  	switch strings.ToLower(value) {
   346  	case "constant":
   347  		*p = PolicyConstant
   348  	case "exponential":
   349  		*p = PolicyExponential
   350  	default:
   351  		return errors.Errorf("unexpected back off policy type: %s", value)
   352  	}
   353  	return nil
   354  }
   355  
   356  // String implements fmt.Stringer and is used for debugging.
   357  func (p PolicyType) String() string {
   358  	switch p {
   359  	case PolicyConstant:
   360  		return "constant"
   361  	case PolicyExponential:
   362  		return "exponential"
   363  	default:
   364  		return ""
   365  	}
   366  }
   367  
   368  func PrefixedBy(input interface{}, prefix string) (interface{}, error) {
   369  	normalized, err := Normalize(input)
   370  	if err != nil {
   371  		// The only error that can come from normalize is if
   372  		// input is a map[interface{}]interface{} and contains
   373  		// a key that is not a string.
   374  		return input, err
   375  	}
   376  	input = normalized
   377  
   378  	if inputMap, ok := input.(map[string]interface{}); ok {
   379  		converted := make(map[string]interface{}, len(inputMap))
   380  		for k, v := range inputMap {
   381  			if strings.HasPrefix(k, prefix) {
   382  				key := uncapitalize(strings.TrimPrefix(k, prefix))
   383  				converted[key] = v
   384  			}
   385  		}
   386  
   387  		return converted, nil
   388  	} else if inputMap, ok := input.(map[string]string); ok {
   389  		converted := make(map[string]string, len(inputMap))
   390  		for k, v := range inputMap {
   391  			if strings.HasPrefix(k, prefix) {
   392  				key := uncapitalize(strings.TrimPrefix(k, prefix))
   393  				converted[key] = v
   394  			}
   395  		}
   396  
   397  		return converted, nil
   398  	}
   399  
   400  	return input, nil
   401  }
   402  
   403  // uncapitalize initial capital letters in `str`.
   404  func uncapitalize(str string) string {
   405  	if len(str) == 0 {
   406  		return str
   407  	}
   408  
   409  	vv := []rune(str) // Introduced later
   410  	vv[0] = unicode.ToLower(vv[0])
   411  
   412  	return string(vv)
   413  }
   414  
   415  //nolint:cyclop
   416  func Normalize(i interface{}) (interface{}, error) {
   417  	var err error
   418  	switch x := i.(type) {
   419  	case map[interface{}]interface{}:
   420  		m2 := map[string]interface{}{}
   421  		for k, v := range x {
   422  			if strKey, ok := k.(string); ok {
   423  				if m2[strKey], err = Normalize(v); err != nil {
   424  					return nil, err
   425  				}
   426  			} else {
   427  				return nil, fmt.Errorf("error parsing config field: %v", k)
   428  			}
   429  		}
   430  
   431  		return m2, nil
   432  	case map[string]interface{}:
   433  		m2 := map[string]interface{}{}
   434  		for k, v := range x {
   435  			if m2[k], err = Normalize(v); err != nil {
   436  				return nil, err
   437  			}
   438  		}
   439  
   440  		return m2, nil
   441  	case []interface{}:
   442  		for i, v := range x {
   443  			if x[i], err = Normalize(v); err != nil {
   444  				return nil, err
   445  			}
   446  		}
   447  	}
   448  
   449  	return i, nil
   450  }