backoff.go 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. package grpcclient
  2. import (
  3. "context"
  4. "math"
  5. "time"
  6. "google.golang.org/grpc/codes"
  7. "google.golang.org/grpc/status"
  8. )
  9. const (
  10. backoffBase = 100 * time.Millisecond
  11. backoffMaxRetries = 8
  12. backoffMax = 30 * time.Second
  13. )
  14. // retryable returns true when the status code is a transient error worth
  15. // retrying within the retry budget.
  16. func retryable(code codes.Code) bool {
  17. switch code {
  18. case codes.Unavailable, codes.ResourceExhausted, codes.Internal:
  19. return true
  20. default:
  21. return false
  22. }
  23. }
  24. // doBackoff sleeps for an interval derived from attempt and the
  25. // server-supplied retryAfterMs. It respects context cancellation.
  26. func doBackoff(ctx context.Context, attempt int, retryAfterMs int) error {
  27. var delay time.Duration
  28. if retryAfterMs > 0 {
  29. delay = time.Duration(retryAfterMs) * time.Millisecond
  30. } else {
  31. delay = time.Duration(float64(backoffBase) * math.Pow(2, float64(attempt)))
  32. if delay > backoffMax {
  33. delay = backoffMax
  34. }
  35. }
  36. select {
  37. case <-ctx.Done():
  38. return ctx.Err()
  39. case <-time.After(delay):
  40. return nil
  41. }
  42. }
  43. // Retry calls fn repeatedly (up to c.maxRetries) until it succeeds,
  44. // ctx is cancelled, or a non-retryable error is returned.
  45. // fn should perform a single gRPC operation that may fail transiently.
  46. func (c *Client) Retry(ctx context.Context, fn func() error) error {
  47. var lastErr error
  48. for attempt := 0; attempt <= c.maxRetries; attempt++ {
  49. if err := ctx.Err(); err != nil {
  50. return err
  51. }
  52. lastErr = fn()
  53. if lastErr == nil {
  54. return nil
  55. }
  56. st, _ := status.FromError(lastErr)
  57. if !retryable(st.Code()) {
  58. return lastErr
  59. }
  60. if attempt < c.maxRetries {
  61. if err := doBackoff(ctx, attempt, 0); err != nil {
  62. return err
  63. }
  64. }
  65. }
  66. return lastErr
  67. }