retry.go

  1// Copyright 2021 Google LLC
  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
 15package metadata
 16
 17import (
 18	"context"
 19	"io"
 20	"math/rand"
 21	"net/http"
 22	"time"
 23)
 24
 25const (
 26	maxRetryAttempts = 5
 27)
 28
 29var (
 30	syscallRetryable = func(error) bool { return false }
 31)
 32
 33// defaultBackoff is basically equivalent to gax.Backoff without the need for
 34// the dependency.
 35type defaultBackoff struct {
 36	max time.Duration
 37	mul float64
 38	cur time.Duration
 39}
 40
 41func (b *defaultBackoff) Pause() time.Duration {
 42	d := time.Duration(1 + rand.Int63n(int64(b.cur)))
 43	b.cur = time.Duration(float64(b.cur) * b.mul)
 44	if b.cur > b.max {
 45		b.cur = b.max
 46	}
 47	return d
 48}
 49
 50// sleep is the equivalent of gax.Sleep without the need for the dependency.
 51func sleep(ctx context.Context, d time.Duration) error {
 52	t := time.NewTimer(d)
 53	select {
 54	case <-ctx.Done():
 55		t.Stop()
 56		return ctx.Err()
 57	case <-t.C:
 58		return nil
 59	}
 60}
 61
 62func newRetryer() *metadataRetryer {
 63	return &metadataRetryer{bo: &defaultBackoff{
 64		cur: 100 * time.Millisecond,
 65		max: 30 * time.Second,
 66		mul: 2,
 67	}}
 68}
 69
 70type backoff interface {
 71	Pause() time.Duration
 72}
 73
 74type metadataRetryer struct {
 75	bo       backoff
 76	attempts int
 77}
 78
 79func (r *metadataRetryer) Retry(status int, err error) (time.Duration, bool) {
 80	if status == http.StatusOK {
 81		return 0, false
 82	}
 83	retryOk := shouldRetry(status, err)
 84	if !retryOk {
 85		return 0, false
 86	}
 87	if r.attempts == maxRetryAttempts {
 88		return 0, false
 89	}
 90	r.attempts++
 91	return r.bo.Pause(), true
 92}
 93
 94func shouldRetry(status int, err error) bool {
 95	if 500 <= status && status <= 599 {
 96		return true
 97	}
 98	if err == io.ErrUnexpectedEOF {
 99		return true
100	}
101	// Transient network errors should be retried.
102	if syscallRetryable(err) {
103		return true
104	}
105	if err, ok := err.(interface{ Temporary() bool }); ok {
106		if err.Temporary() {
107			return true
108		}
109	}
110	if err, ok := err.(interface{ Unwrap() error }); ok {
111		return shouldRetry(status, err.Unwrap())
112	}
113	return false
114}