summaryrefslogtreecommitdiff
path: root/vendor/google.golang.org/api/internal/gensupport/resumable.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/google.golang.org/api/internal/gensupport/resumable.go')
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/resumable.go128
1 files changed, 67 insertions, 61 deletions
diff --git a/vendor/google.golang.org/api/internal/gensupport/resumable.go b/vendor/google.golang.org/api/internal/gensupport/resumable.go
index edc87ec24..f168ea6d2 100644
--- a/vendor/google.golang.org/api/internal/gensupport/resumable.go
+++ b/vendor/google.golang.org/api/internal/gensupport/resumable.go
@@ -10,34 +10,12 @@ import (
"fmt"
"io"
"net/http"
+ "strings"
"sync"
"time"
- gax "github.com/googleapis/gax-go/v2"
-)
-
-// 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 (
- retryDeadline = 32 * time.Second
- backoff = func() Backoff {
- return &gax.Backoff{Initial: 100 * time.Millisecond}
- }
- // isRetryable 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
+ "github.com/google/uuid"
+ "google.golang.org/api/internal"
)
// ResumableUpload is used by the generated APIs to provide resumable uploads.
@@ -57,6 +35,18 @@ type ResumableUpload struct {
// Callback is an optional function that will be periodically called with the cumulative number of bytes uploaded.
Callback func(int64)
+
+ // Retry optionally configures retries for requests made against the upload.
+ Retry *RetryConfig
+
+ // ChunkRetryDeadline configures the per-chunk deadline after which no further
+ // retries should happen.
+ ChunkRetryDeadline time.Duration
+
+ // Track current request invocation ID and attempt count for retry metric
+ // headers.
+ invocationID string
+ attempts int
}
// Progress returns the number of bytes uploaded at this point.
@@ -91,6 +81,10 @@ func (rx *ResumableUpload) doUploadRequest(ctx context.Context, data io.Reader,
req.Header.Set("Content-Type", rx.MediaType)
req.Header.Set("User-Agent", rx.UserAgent)
+ baseXGoogHeader := "gl-go/" + GoVersion() + " gdcl/" + internal.Version
+ invocationHeader := fmt.Sprintf("gccl-invocation-id/%s gccl-attempt-count/%d", rx.invocationID, rx.attempts)
+ req.Header.Set("X-Goog-Api-Client", strings.Join([]string{baseXGoogHeader, invocationHeader}, " "))
+
// Google's upload endpoint uses status code 308 for a
// different purpose than the "308 Permanent Redirect"
// since-standardized in RFC 7238. Because of the conflict in
@@ -174,29 +168,69 @@ func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err
}
return nil, err
}
+ // This case is very unlikely but possible only if rx.ChunkRetryDeadline is
+ // set to a very small value, in which case no requests will be sent before
+ // the deadline. Return an error to avoid causing a panic.
+ if resp == nil {
+ return nil, fmt.Errorf("upload request to %v not sent, choose larger value for ChunkRetryDealine", rx.URI)
+ }
return resp, nil
}
+ // Configure retryable error criteria.
+ errorFunc := rx.Retry.errorFunc()
+
+ // Configure per-chunk retry deadline.
+ var retryDeadline time.Duration
+ if rx.ChunkRetryDeadline != 0 {
+ retryDeadline = rx.ChunkRetryDeadline
+ } else {
+ retryDeadline = defaultRetryDeadline
+ }
// Send all chunks.
for {
var pause time.Duration
- // Each chunk gets its own initialized-at-zero retry.
- bo := backoff()
- quitAfter := time.After(retryDeadline)
+ // Each chunk gets its own initialized-at-zero backoff and invocation ID.
+ bo := rx.Retry.backoff()
+ quitAfterTimer := time.NewTimer(retryDeadline)
+ rx.attempts = 1
+ rx.invocationID = uuid.New().String()
// Retry loop for a single chunk.
for {
+ pauseTimer := time.NewTimer(pause)
select {
case <-ctx.Done():
+ quitAfterTimer.Stop()
+ pauseTimer.Stop()
if err == nil {
err = ctx.Err()
}
return prepareReturn(resp, err)
- case <-time.After(pause):
- case <-quitAfter:
+ case <-pauseTimer.C:
+ case <-quitAfterTimer.C:
+ pauseTimer.Stop()
return prepareReturn(resp, err)
}
+ pauseTimer.Stop()
+
+ // Check for context cancellation or timeout once more. If more than one
+ // case in the select statement above was satisfied at the same time, Go
+ // will choose one arbitrarily.
+ // That can cause an operation to go through even if the context was
+ // canceled before or the timeout was reached.
+ select {
+ case <-ctx.Done():
+ quitAfterTimer.Stop()
+ if err == nil {
+ err = ctx.Err()
+ }
+ return prepareReturn(resp, err)
+ case <-quitAfterTimer.C:
+ return prepareReturn(resp, err)
+ default:
+ }
resp, err = rx.transferChunk(ctx)
@@ -206,10 +240,12 @@ func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err
}
// Check if we should retry the request.
- if !shouldRetry(status, err) {
+ if !errorFunc(status, err) {
+ quitAfterTimer.Stop()
break
}
+ rx.attempts++
pause = bo.Pause()
if resp != nil && resp.Body != nil {
resp.Body.Close()
@@ -226,33 +262,3 @@ func (rx *ResumableUpload) Upload(ctx context.Context) (resp *http.Response, err
return prepareReturn(resp, err)
}
}
-
-// shouldRetry indicates whether an error is retryable for the purposes of this
-// package, following 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 {
- 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
- }
- }
- // 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
-}