retry.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package fx
  2. import (
  3. "context"
  4. "errors"
  5. "time"
  6. "github.com/zeromicro/go-zero/core/errorx"
  7. )
  8. const defaultRetryTimes = 3
  9. var errTimeout = errors.New("retry timeout")
  10. type (
  11. // RetryOption defines the method to customize DoWithRetry.
  12. RetryOption func(*retryOptions)
  13. retryOptions struct {
  14. times int
  15. interval time.Duration
  16. timeout time.Duration
  17. }
  18. )
  19. // DoWithRetry runs fn, and retries if failed. Default to retry 3 times.
  20. // Note that if the fn function accesses global variables outside the function and performs modification operations,
  21. // it is best to lock them, otherwise there may be data race issues
  22. func DoWithRetry(fn func() error, opts ...RetryOption) error {
  23. return retry(fn, opts...)
  24. }
  25. // DoWithRetryCtx runs fn, and retries if failed. Default to retry 3 times.
  26. // fn retryCount indicates the current number of retries,starting from 0
  27. // Note that if the fn function accesses global variables outside the function and performs modification operations,
  28. // it is best to lock them, otherwise there may be data race issues
  29. func DoWithRetryCtx(fn func(ctx context.Context, retryCount int) error, opts ...RetryOption) error {
  30. return retry(fn, opts...)
  31. }
  32. func retry(fn interface{}, opts ...RetryOption) error {
  33. options := newRetryOptions()
  34. for _, opt := range opts {
  35. opt(options)
  36. }
  37. sign := make(chan error, 1)
  38. var berr errorx.BatchError
  39. var cancelFunc context.CancelFunc
  40. ctx := context.Background()
  41. if options.timeout > 0 {
  42. ctx, cancelFunc = context.WithTimeout(ctx, options.timeout)
  43. defer cancelFunc()
  44. }
  45. for i := 0; i < options.times; i++ {
  46. go func(retryCount int) {
  47. switch f := fn.(type) {
  48. case func() error:
  49. sign <- f()
  50. case func(ctx context.Context, retryCount int) error:
  51. sign <- f(ctx, retryCount)
  52. }
  53. }(i)
  54. select {
  55. case err := <-sign:
  56. if err != nil {
  57. berr.Add(err)
  58. } else {
  59. return nil
  60. }
  61. case <-ctx.Done():
  62. berr.Add(errTimeout)
  63. return berr.Err()
  64. }
  65. if options.interval > 0 {
  66. select {
  67. case <-ctx.Done():
  68. berr.Add(errTimeout)
  69. return berr.Err()
  70. case <-time.After(options.interval):
  71. }
  72. }
  73. }
  74. return berr.Err()
  75. }
  76. // WithRetry customize a DoWithRetry call with given retry times.
  77. func WithRetry(times int) RetryOption {
  78. return func(options *retryOptions) {
  79. options.times = times
  80. }
  81. }
  82. func WithInterval(interval time.Duration) RetryOption {
  83. return func(options *retryOptions) {
  84. options.interval = interval
  85. }
  86. }
  87. func WithTimeout(timeout time.Duration) RetryOption {
  88. return func(options *retryOptions) {
  89. options.timeout = timeout
  90. }
  91. }
  92. func newRetryOptions() *retryOptions {
  93. return &retryOptions{
  94. times: defaultRetryTimes,
  95. interval: 0,
  96. timeout: 0,
  97. }
  98. }