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 }