retry.go 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. // Copyright 2017 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package gensupport
  15. import (
  16. "io"
  17. "net"
  18. "net/http"
  19. "time"
  20. "golang.org/x/net/context"
  21. )
  22. // Retry invokes the given function, retrying it multiple times if the connection failed or
  23. // the HTTP status response indicates the request should be attempted again. ctx may be nil.
  24. func Retry(ctx context.Context, f func() (*http.Response, error), backoff BackoffStrategy) (*http.Response, error) {
  25. for {
  26. resp, err := f()
  27. var status int
  28. if resp != nil {
  29. status = resp.StatusCode
  30. }
  31. // Return if we shouldn't retry.
  32. pause, retry := backoff.Pause()
  33. if !shouldRetry(status, err) || !retry {
  34. return resp, err
  35. }
  36. // Ensure the response body is closed, if any.
  37. if resp != nil && resp.Body != nil {
  38. resp.Body.Close()
  39. }
  40. // Pause, but still listen to ctx.Done if context is not nil.
  41. var done <-chan struct{}
  42. if ctx != nil {
  43. done = ctx.Done()
  44. }
  45. select {
  46. case <-done:
  47. return nil, ctx.Err()
  48. case <-time.After(pause):
  49. }
  50. }
  51. }
  52. // DefaultBackoffStrategy returns a default strategy to use for retrying failed upload requests.
  53. func DefaultBackoffStrategy() BackoffStrategy {
  54. return &ExponentialBackoff{
  55. Base: 250 * time.Millisecond,
  56. Max: 16 * time.Second,
  57. }
  58. }
  59. // shouldRetry returns true if the HTTP response / error indicates that the
  60. // request should be attempted again.
  61. func shouldRetry(status int, err error) bool {
  62. if 500 <= status && status <= 599 {
  63. return true
  64. }
  65. if status == statusTooManyRequests {
  66. return true
  67. }
  68. if err == io.ErrUnexpectedEOF {
  69. return true
  70. }
  71. if err, ok := err.(net.Error); ok {
  72. return err.Temporary()
  73. }
  74. return false
  75. }