github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/retry/retry.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package retry 18 19 import ( 20 "context" 21 "time" 22 23 "github.com/kaleido-io/firefly/internal/i18n" 24 "github.com/kaleido-io/firefly/internal/log" 25 ) 26 27 const ( 28 defaultFactor = 2.0 29 ) 30 31 // Retry is a concurrency safe retry structure that configures a simple backoff retry mechanism 32 type Retry struct { 33 InitialDelay time.Duration 34 MaximumDelay time.Duration 35 Factor float64 36 } 37 38 // DoCustomLog disables the automatic attempt logging, so the caller should do logging for each attempt 39 func (r *Retry) DoCustomLog(ctx context.Context, f func(attempt int) (retry bool, err error)) error { 40 return r.Do(ctx, "", f) 41 } 42 43 // Do invokes the function until the function returns false, or the retry pops. 44 // This simple interface doesn't pass through errors or return values, on the basis 45 // you'll be using a closure for that. 46 func (r *Retry) Do(ctx context.Context, logDescription string, f func(attempt int) (retry bool, err error)) error { 47 attempt := 0 48 delay := r.InitialDelay 49 factor := r.Factor 50 if factor < 1 { // Can't reduce 51 factor = defaultFactor 52 } 53 for { 54 attempt++ 55 retry, err := f(attempt) 56 if err != nil && logDescription != "" { 57 log.L(ctx).Errorf("%s attempt %d: %s", logDescription, attempt, err) 58 } 59 if !retry || err == nil { 60 return err 61 } 62 63 // Check the context isn't cancelled 64 select { 65 case <-ctx.Done(): 66 return i18n.NewError(ctx, i18n.MsgContextCanceled) 67 default: 68 } 69 70 // Limit the delay based on the context deadline and maximum delay 71 deadline, dok := ctx.Deadline() 72 now := time.Now() 73 if delay > r.MaximumDelay { 74 delay = r.MaximumDelay 75 } 76 if dok { 77 timeleft := deadline.Sub(now) 78 if timeleft < delay { 79 delay = timeleft 80 } 81 } 82 83 // Sleep and set the delay for next time 84 time.Sleep(delay) 85 delay = time.Duration(float64(delay) * factor) 86 } 87 }