summaryrefslogtreecommitdiff
path: root/vendor/google.golang.org/api/internal/gensupport/retry.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/google.golang.org/api/internal/gensupport/retry.go')
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/retry.go121
1 files changed, 121 insertions, 0 deletions
diff --git a/vendor/google.golang.org/api/internal/gensupport/retry.go b/vendor/google.golang.org/api/internal/gensupport/retry.go
new file mode 100644
index 0000000..20b57d9
--- /dev/null
+++ b/vendor/google.golang.org/api/internal/gensupport/retry.go
@@ -0,0 +1,121 @@
+// Copyright 2021 Google LLC.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gensupport
+
+import (
+ "errors"
+ "io"
+ "net"
+ "strings"
+ "time"
+
+ "github.com/googleapis/gax-go/v2"
+ "google.golang.org/api/googleapi"
+)
+
+// Backoff is an interface around gax.Backoff's Pause method, allowing tests to provide their
+// own implementation.
+type Backoff interface {
+ Pause() time.Duration
+}
+
+// These are declared as global variables so that tests can overwrite them.
+var (
+ // Default per-chunk deadline for resumable uploads.
+ defaultRetryDeadline = 32 * time.Second
+ // Default backoff timer.
+ backoff = func() Backoff {
+ return &gax.Backoff{Initial: 100 * time.Millisecond}
+ }
+ // syscallRetryable is a platform-specific hook, specified in retryable_linux.go
+ syscallRetryable func(error) bool = func(err error) bool { return false }
+)
+
+const (
+ // statusTooManyRequests is returned by the storage API if the
+ // per-project limits have been temporarily exceeded. The request
+ // should be retried.
+ // https://cloud.google.com/storage/docs/json_api/v1/status-codes#standardcodes
+ statusTooManyRequests = 429
+
+ // statusRequestTimeout is returned by the storage API if the
+ // upload connection was broken. The request should be retried.
+ statusRequestTimeout = 408
+)
+
+// shouldRetry indicates whether an error is retryable for the purposes of this
+// package, unless a ShouldRetry func is specified by the RetryConfig instead.
+// It follows guidance from
+// https://cloud.google.com/storage/docs/exponential-backoff .
+func shouldRetry(status int, err error) bool {
+ if 500 <= status && status <= 599 {
+ return true
+ }
+ if status == statusTooManyRequests || status == statusRequestTimeout {
+ return true
+ }
+ if err == io.ErrUnexpectedEOF {
+ return true
+ }
+ // Transient network errors should be retried.
+ if syscallRetryable(err) {
+ return true
+ }
+ if err, ok := err.(interface{ Temporary() bool }); ok {
+ if err.Temporary() {
+ return true
+ }
+ }
+ var opErr *net.OpError
+ if errors.As(err, &opErr) {
+ if strings.Contains(opErr.Error(), "use of closed network connection") {
+ // TODO: check against net.ErrClosed (go 1.16+) instead of string
+ return true
+ }
+ }
+
+ // If Go 1.13 error unwrapping is available, use this to examine wrapped
+ // errors.
+ if err, ok := err.(interface{ Unwrap() error }); ok {
+ return shouldRetry(status, err.Unwrap())
+ }
+ return false
+}
+
+// RetryConfig allows configuration of backoff timing and retryable errors.
+type RetryConfig struct {
+ Backoff *gax.Backoff
+ ShouldRetry func(err error) bool
+}
+
+// Get a new backoff object based on the configured values.
+func (r *RetryConfig) backoff() Backoff {
+ if r == nil || r.Backoff == nil {
+ return backoff()
+ }
+ return &gax.Backoff{
+ Initial: r.Backoff.Initial,
+ Max: r.Backoff.Max,
+ Multiplier: r.Backoff.Multiplier,
+ }
+}
+
+// This is kind of hacky; it is necessary because ShouldRetry expects to
+// handle HTTP errors via googleapi.Error, but the error has not yet been
+// wrapped with a googleapi.Error at this layer, and the ErrorFunc type
+// in the manual layer does not pass in a status explicitly as it does
+// here. So, we must wrap error status codes in a googleapi.Error so that
+// ShouldRetry can parse this correctly.
+func (r *RetryConfig) errorFunc() func(status int, err error) bool {
+ if r == nil || r.ShouldRetry == nil {
+ return shouldRetry
+ }
+ return func(status int, err error) bool {
+ if status >= 400 {
+ return r.ShouldRetry(&googleapi.Error{Code: status})
+ }
+ return r.ShouldRetry(err)
+ }
+}