summaryrefslogtreecommitdiff
path: root/vendor/google.golang.org/api/internal/gensupport/send.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/google.golang.org/api/internal/gensupport/send.go')
-rw-r--r--vendor/google.golang.org/api/internal/gensupport/send.go79
1 files changed, 68 insertions, 11 deletions
diff --git a/vendor/google.golang.org/api/internal/gensupport/send.go b/vendor/google.golang.org/api/internal/gensupport/send.go
index 276d6f696..85c7bcbfd 100644
--- a/vendor/google.golang.org/api/internal/gensupport/send.go
+++ b/vendor/google.golang.org/api/internal/gensupport/send.go
@@ -8,10 +8,36 @@ import (
"context"
"encoding/json"
"errors"
+ "fmt"
"net/http"
+ "strings"
"time"
+
+ "github.com/google/uuid"
+ "github.com/googleapis/gax-go/v2"
)
+// Use this error type to return an error which allows introspection of both
+// the context error and the error from the service.
+type wrappedCallErr struct {
+ ctxErr error
+ wrappedErr error
+}
+
+func (e wrappedCallErr) Error() string {
+ return fmt.Sprintf("retry failed with %v; last error: %v", e.ctxErr, e.wrappedErr)
+}
+
+func (e wrappedCallErr) Unwrap() error {
+ return e.wrappedErr
+}
+
+// Is allows errors.Is to match the error from the call as well as context
+// sentinel errors.
+func (e wrappedCallErr) Is(target error) bool {
+ return errors.Is(e.ctxErr, target) || errors.Is(e.wrappedErr, target)
+}
+
// SendRequest sends a single HTTP request using the given client.
// If ctx is non-nil, it calls all hooks, then sends the request with
// req.WithContext, then calls any functions returned by the hooks in
@@ -50,7 +76,7 @@ func send(ctx context.Context, client *http.Client, req *http.Request) (*http.Re
// If ctx is non-nil, it calls all hooks, then sends the request with
// req.WithContext, then calls any functions returned by the hooks in
// reverse order.
-func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
+func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
// Disallow Accept-Encoding because it interferes with the automatic gzip handling
// done by the default http.Transport. See https://github.com/google/google-api-go-client/issues/219.
if _, ok := req.Header["Accept-Encoding"]; ok {
@@ -59,32 +85,62 @@ func SendRequestWithRetry(ctx context.Context, client *http.Client, req *http.Re
if ctx == nil {
return client.Do(req)
}
- return sendAndRetry(ctx, client, req)
+ return sendAndRetry(ctx, client, req, retry)
}
-func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
+func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request, retry *RetryConfig) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
}
var resp *http.Response
var err error
+ attempts := 1
+ invocationID := uuid.New().String()
+ baseXGoogHeader := req.Header.Get("X-Goog-Api-Client")
// Loop to retry the request, up to the context deadline.
var pause time.Duration
- bo := backoff()
+ var bo Backoff
+ if retry != nil && retry.Backoff != nil {
+ bo = &gax.Backoff{
+ Initial: retry.Backoff.Initial,
+ Max: retry.Backoff.Max,
+ Multiplier: retry.Backoff.Multiplier,
+ }
+ } else {
+ bo = backoff()
+ }
+
+ var errorFunc = retry.errorFunc()
for {
+ t := time.NewTimer(pause)
select {
case <-ctx.Done():
- // If we got an error, and the context has been canceled,
- // the context's error is probably more useful.
- if err == nil {
- err = ctx.Err()
+ t.Stop()
+ // If we got an error and the context has been canceled, return an error acknowledging
+ // both the context cancelation and the service error.
+ if err != nil {
+ return resp, wrappedCallErr{ctx.Err(), err}
+ }
+ return resp, ctx.Err()
+ case <-t.C:
+ }
+
+ if ctx.Err() != nil {
+ // Check for context cancellation once more. If more than one case in a
+ // select is 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.
+ if err != nil {
+ return resp, wrappedCallErr{ctx.Err(), err}
}
- return resp, err
- case <-time.After(pause):
+ return resp, ctx.Err()
}
+ invocationHeader := fmt.Sprintf("gccl-invocation-id/%s gccl-attempt-count/%d", invocationID, attempts)
+ xGoogHeader := strings.Join([]string{invocationHeader, baseXGoogHeader}, " ")
+ req.Header.Set("X-Goog-Api-Client", xGoogHeader)
resp, err = client.Do(req.WithContext(ctx))
@@ -96,9 +152,10 @@ func sendAndRetry(ctx context.Context, client *http.Client, req *http.Request) (
// Check if we can retry the request. A retry can only be done if the error
// is retryable and the request body can be re-created using GetBody (this
// will not be possible if the body was unbuffered).
- if req.GetBody == nil || !shouldRetry(status, err) {
+ if req.GetBody == nil || !errorFunc(status, err) {
break
}
+ attempts++
var errBody error
req.Body, errBody = req.GetBody()
if errBody != nil {