github.com/blend/go-sdk@v1.20220411.3/retry/retry.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package retry 9 10 import ( 11 "context" 12 "time" 13 14 "github.com/blend/go-sdk/async" 15 "github.com/blend/go-sdk/ex" 16 ) 17 18 var ( 19 _ async.Interceptor = (*Retrier)(nil) 20 ) 21 22 // Retry calls an actioner with retries. 23 func Retry(ctx context.Context, action Actioner, args interface{}, opts ...Option) (interface{}, error) { 24 return New(opts...).Intercept(action).Action(ctx, args) 25 } 26 27 // New wraps an actioner with retries. 28 func New(opts ...Option) *Retrier { 29 retrier := Retrier{} 30 Defaults(&retrier) 31 for _, opt := range opts { 32 opt(&retrier) 33 } 34 return &retrier 35 } 36 37 // Retrier is the retry agent. 38 type Retrier struct { 39 MaxAttempts uint 40 DelayProvider DelayProvider 41 ShouldRetryProvider ShouldRetryProvider 42 } 43 44 // Intercept calls a function and retries on error or if a should retry provider 45 // is set, based on the should retry result. 46 func (r Retrier) Intercept(action Actioner) Actioner { 47 return ActionerFunc(func(ctx context.Context, args interface{}) (res interface{}, err error) { 48 var attempt uint 49 var alarm *time.Timer 50 51 // treat maxAttempts == 0 as mostly unbounded 52 // we have to keep attempts for the delay provider 53 for attempt = 0; (r.MaxAttempts) == 0 || (attempt < r.MaxAttempts); attempt++ { 54 func() { 55 defer func() { 56 if r := recover(); r != nil { 57 err = ex.New(r) 58 } 59 }() 60 res, err = action.Action(ctx, args) 61 }() 62 if err == nil { 63 return 64 } 65 if !r.ShouldRetryProvider(err) { 66 return 67 } 68 69 // use a (somewhat) persistent alarm reference 70 alarm = time.NewTimer(r.DelayProvider(ctx, attempt)) 71 select { 72 case <-ctx.Done(): 73 alarm.Stop() 74 err = context.Canceled 75 return 76 case <-alarm.C: 77 alarm.Stop() 78 } 79 } 80 return 81 }) 82 }