retry.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // Copyright 2021 Google LLC.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gensupport
  5. import (
  6. "errors"
  7. "io"
  8. "net"
  9. "strings"
  10. "time"
  11. "github.com/googleapis/gax-go/v2"
  12. "google.golang.org/api/googleapi"
  13. )
  14. // Backoff is an interface around gax.Backoff's Pause method, allowing tests to provide their
  15. // own implementation.
  16. type Backoff interface {
  17. Pause() time.Duration
  18. }
  19. // These are declared as global variables so that tests can overwrite them.
  20. var (
  21. // Default per-chunk deadline for resumable uploads.
  22. defaultRetryDeadline = 32 * time.Second
  23. // Default backoff timer.
  24. backoff = func() Backoff {
  25. return &gax.Backoff{Initial: 100 * time.Millisecond}
  26. }
  27. // syscallRetryable is a platform-specific hook, specified in retryable_linux.go
  28. syscallRetryable func(error) bool = func(err error) bool { return false }
  29. )
  30. const (
  31. // statusTooManyRequests is returned by the storage API if the
  32. // per-project limits have been temporarily exceeded. The request
  33. // should be retried.
  34. // https://cloud.google.com/storage/docs/json_api/v1/status-codes#standardcodes
  35. statusTooManyRequests = 429
  36. // statusRequestTimeout is returned by the storage API if the
  37. // upload connection was broken. The request should be retried.
  38. statusRequestTimeout = 408
  39. )
  40. // shouldRetry indicates whether an error is retryable for the purposes of this
  41. // package, unless a ShouldRetry func is specified by the RetryConfig instead.
  42. // It follows guidance from
  43. // https://cloud.google.com/storage/docs/exponential-backoff .
  44. func shouldRetry(status int, err error) bool {
  45. if 500 <= status && status <= 599 {
  46. return true
  47. }
  48. if status == statusTooManyRequests || status == statusRequestTimeout {
  49. return true
  50. }
  51. if err == io.ErrUnexpectedEOF {
  52. return true
  53. }
  54. // Transient network errors should be retried.
  55. if syscallRetryable(err) {
  56. return true
  57. }
  58. if err, ok := err.(interface{ Temporary() bool }); ok {
  59. if err.Temporary() {
  60. return true
  61. }
  62. }
  63. var opErr *net.OpError
  64. if errors.As(err, &opErr) {
  65. if strings.Contains(opErr.Error(), "use of closed network connection") {
  66. // TODO: check against net.ErrClosed (go 1.16+) instead of string
  67. return true
  68. }
  69. }
  70. // If Go 1.13 error unwrapping is available, use this to examine wrapped
  71. // errors.
  72. if err, ok := err.(interface{ Unwrap() error }); ok {
  73. return shouldRetry(status, err.Unwrap())
  74. }
  75. return false
  76. }
  77. // RetryConfig allows configuration of backoff timing and retryable errors.
  78. type RetryConfig struct {
  79. Backoff *gax.Backoff
  80. ShouldRetry func(err error) bool
  81. }
  82. // Get a new backoff object based on the configured values.
  83. func (r *RetryConfig) backoff() Backoff {
  84. if r == nil || r.Backoff == nil {
  85. return backoff()
  86. }
  87. return &gax.Backoff{
  88. Initial: r.Backoff.Initial,
  89. Max: r.Backoff.Max,
  90. Multiplier: r.Backoff.Multiplier,
  91. }
  92. }
  93. // This is kind of hacky; it is necessary because ShouldRetry expects to
  94. // handle HTTP errors via googleapi.Error, but the error has not yet been
  95. // wrapped with a googleapi.Error at this layer, and the ErrorFunc type
  96. // in the manual layer does not pass in a status explicitly as it does
  97. // here. So, we must wrap error status codes in a googleapi.Error so that
  98. // ShouldRetry can parse this correctly.
  99. func (r *RetryConfig) errorFunc() func(status int, err error) bool {
  100. if r == nil || r.ShouldRetry == nil {
  101. return shouldRetry
  102. }
  103. return func(status int, err error) bool {
  104. if status >= 400 {
  105. return r.ShouldRetry(&googleapi.Error{Code: status})
  106. }
  107. return r.ShouldRetry(err)
  108. }
  109. }