1 package waiter
2 3 import (
4 "fmt"
5 "math"
6 "time"
7 8 "github.com/aws/smithy-go/rand"
9 )
10 11 // ComputeDelay computes delay between waiter attempts. The function takes in a current attempt count,
12 // minimum delay, maximum delay, and remaining wait time for waiter as input. The inputs minDelay and maxDelay
13 // must always be greater than 0, along with minDelay lesser than or equal to maxDelay.
14 //
15 // Returns the computed delay and if next attempt count is possible within the given input time constraints.
16 // Note that the zeroth attempt results in no delay.
17 func ComputeDelay(attempt int64, minDelay, maxDelay, remainingTime time.Duration) (delay time.Duration, err error) {
18 // zeroth attempt, no delay
19 if attempt <= 0 {
20 return 0, nil
21 }
22 23 // remainingTime is zero or less, no delay
24 if remainingTime <= 0 {
25 return 0, nil
26 }
27 28 // validate min delay is greater than 0
29 if minDelay == 0 {
30 return 0, fmt.Errorf("minDelay must be greater than zero when computing Delay")
31 }
32 33 // validate max delay is greater than 0
34 if maxDelay == 0 {
35 return 0, fmt.Errorf("maxDelay must be greater than zero when computing Delay")
36 }
37 38 // Get attempt ceiling to prevent integer overflow.
39 attemptCeiling := (math.Log(float64(maxDelay/minDelay)) / math.Log(2)) + 1
40 41 if attempt > int64(attemptCeiling) {
42 delay = maxDelay
43 } else {
44 // Compute exponential delay based on attempt.
45 ri := 1 << uint64(attempt-1)
46 // compute delay
47 delay = minDelay * time.Duration(ri)
48 }
49 50 if delay != minDelay {
51 // randomize to get jitter between min delay and delay value
52 d, err := rand.CryptoRandInt63n(int64(delay - minDelay))
53 if err != nil {
54 return 0, fmt.Errorf("error computing retry jitter, %w", err)
55 }
56 57 delay = time.Duration(d) + minDelay
58 }
59 60 // check if this is the last attempt possible and compute delay accordingly
61 if remainingTime-delay <= minDelay {
62 delay = remainingTime - minDelay
63 }
64 65 return delay, nil
66 }
67