retrier.go 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. // Package retrier implements the "retriable" resiliency pattern for Go.
  2. package retrier
  3. import (
  4. "math/rand"
  5. "sync"
  6. "time"
  7. )
  8. // Retrier implements the "retriable" resiliency pattern, abstracting out the process of retrying a failed action
  9. // a certain number of times with an optional back-off between each retry.
  10. type Retrier struct {
  11. backoff []time.Duration
  12. class Classifier
  13. jitter float64
  14. rand *rand.Rand
  15. randMu sync.Mutex
  16. }
  17. // New constructs a Retrier with the given backoff pattern and classifier. The length of the backoff pattern
  18. // indicates how many times an action will be retried, and the value at each index indicates the amount of time
  19. // waited before each subsequent retry. The classifier is used to determine which errors should be retried and
  20. // which should cause the retrier to fail fast. The DefaultClassifier is used if nil is passed.
  21. func New(backoff []time.Duration, class Classifier) *Retrier {
  22. if class == nil {
  23. class = DefaultClassifier{}
  24. }
  25. return &Retrier{
  26. backoff: backoff,
  27. class: class,
  28. rand: rand.New(rand.NewSource(time.Now().UnixNano())),
  29. }
  30. }
  31. // Run executes the given work function, then classifies its return value based on the classifier used
  32. // to construct the Retrier. If the result is Succeed or Fail, the return value of the work function is
  33. // returned to the caller. If the result is Retry, then Run sleeps according to the its backoff policy
  34. // before retrying. If the total number of retries is exceeded then the return value of the work function
  35. // is returned to the caller regardless.
  36. func (r *Retrier) Run(work func() error) error {
  37. retries := 0
  38. for {
  39. ret := work()
  40. switch r.class.Classify(ret) {
  41. case Succeed, Fail:
  42. return ret
  43. case Retry:
  44. if retries >= len(r.backoff) {
  45. return ret
  46. }
  47. time.Sleep(r.calcSleep(retries))
  48. retries++
  49. }
  50. }
  51. }
  52. func (r *Retrier) calcSleep(i int) time.Duration {
  53. // lock unsafe rand prng
  54. r.randMu.Lock()
  55. defer r.randMu.Unlock()
  56. // take a random float in the range (-r.jitter, +r.jitter) and multiply it by the base amount
  57. return r.backoff[i] + time.Duration(((r.rand.Float64()*2)-1)*r.jitter*float64(r.backoff[i]))
  58. }
  59. // SetJitter sets the amount of jitter on each back-off to a factor between 0.0 and 1.0 (values outside this range
  60. // are silently ignored). When a retry occurs, the back-off is adjusted by a random amount up to this value.
  61. func (r *Retrier) SetJitter(jit float64) {
  62. if jit < 0 || jit > 1 {
  63. return
  64. }
  65. r.jitter = jit
  66. }