go.fuchsia.dev/jiri@v0.0.0-20240502161911-b66513b29486/retry/retry.go (about) 1 // Copyright 2015 The Vanadium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package retry provides a facility for retrying function 6 // invocations. 7 package retry 8 9 import ( 10 "fmt" 11 "math" 12 "math/rand" 13 "time" 14 15 "go.fuchsia.dev/jiri" 16 ) 17 18 type RetryOpt interface { 19 retryOpt() 20 } 21 22 type AttemptsOpt int 23 24 func (a AttemptsOpt) retryOpt() {} 25 26 type IntervalOpt time.Duration 27 28 func (i IntervalOpt) retryOpt() {} 29 30 const ( 31 defaultAttempts = 3 32 defaultInterval = 5 * time.Second 33 ) 34 35 type exponentialBackoff struct { 36 InitialInterval float64 37 MaxInterval float64 38 Multiplier float64 39 Iteration int 40 Rand *rand.Rand 41 } 42 43 func newExponentialBackoff(initialInterval float64, maxInterval float64, multiplier float64) *exponentialBackoff { 44 e := &exponentialBackoff{ 45 InitialInterval: initialInterval, 46 MaxInterval: maxInterval, 47 Multiplier: multiplier, 48 Iteration: 0, 49 Rand: rand.New(rand.NewSource(time.Now().UnixNano())), 50 } 51 return e 52 } 53 54 func (e *exponentialBackoff) nextBackoff() time.Duration { 55 next := e.InitialInterval*math.Pow(e.Multiplier, float64(e.Iteration)) + 10*e.Rand.Float64() 56 e.Iteration++ 57 if next > e.MaxInterval { 58 next = e.MaxInterval 59 } 60 return time.Duration(float64(time.Second) * next) 61 } 62 63 // Function retries the given function for the given number of 64 // attempts at the given interval. 65 func Function(jirix *jiri.X, fn func() error, task string, opts ...RetryOpt) error { 66 attempts, interval := defaultAttempts, defaultInterval 67 for _, opt := range opts { 68 switch typedOpt := opt.(type) { 69 case AttemptsOpt: 70 attempts = int(typedOpt) 71 case IntervalOpt: 72 interval = time.Duration(typedOpt) 73 } 74 } 75 76 backoff := newExponentialBackoff(float64(interval), 64, 2) 77 var err error 78 for i := 1; i <= attempts; i++ { 79 if i > 1 { 80 jirix.Logger.Infof("Attempt %d/%d: %s\n\n", i, attempts, task) 81 } 82 if err = fn(); err == nil { 83 return nil 84 } 85 if i < attempts { 86 jirix.Logger.Errorf("%s\n\n", err) 87 backoffInterval := backoff.nextBackoff() 88 jirix.Logger.Infof("Wait for %s before next attempt...: %s\n\n", backoffInterval, task) 89 time.Sleep(backoffInterval) 90 } 91 } 92 if attempts > 1 { 93 return fmt.Errorf("%q failed %d times in a row, Last error: %s", task, attempts, err) 94 } 95 return err 96 }