diff --git a/clientutil/retry.go b/clientutil/retry.go index 054d301607ea3575da8896e9110e39ada5fba3a2..3ca7b51a48289a18cd829947cbdbc400392ffc93 100644 --- a/clientutil/retry.go +++ b/clientutil/retry.go @@ -2,7 +2,6 @@ package clientutil import ( "errors" - "net" "net/http" "time" @@ -18,14 +17,37 @@ func NewExponentialBackOff() *backoff.ExponentialBackOff { return b } -// Retry operation op until it succeeds according to the backoff policy b. +// A temporary (retriable) error is something that has a Temporary method. +type tempError interface { + Temporary() bool +} + +type tempErrorWrapper struct { + error +} + +func (t tempErrorWrapper) Temporary() bool { return true } + +// TempError makes a temporary (retriable) error out of a normal error. +func TempError(err error) error { + return tempErrorWrapper{err} +} + +// Retry operation op until it succeeds according to the backoff +// policy b. +// +// Note that this function reverses the error semantics of +// backoff.Operation: all errors are permanent unless explicitly +// marked as temporary (i.e. they have a Temporary() method that +// returns true). This is to better align with the errors returned by +// the net package. func Retry(op backoff.Operation, b backoff.BackOff) error { innerOp := func() error { err := op() if err == nil { return err } - if netErr, ok := err.(net.Error); ok && netErr.Temporary() { + if tmpErr, ok := err.(tempError); ok && tmpErr.Temporary() { return err } return backoff.Permanent(err) @@ -33,7 +55,7 @@ func Retry(op backoff.Operation, b backoff.BackOff) error { return backoff.Retry(innerOp, b) } -var errHTTPBackOff = errors.New("temporary http error") +var errHTTPBackOff = TempError(errors.New("temporary http error")) func isStatusTemporary(code int) bool { switch code { @@ -46,7 +68,8 @@ func isStatusTemporary(code int) bool { // RetryHTTPDo retries an HTTP request until it succeeds, according to // the backoff policy b. It will retry on temporary network errors and -// upon receiving specific temporary HTTP errors. +// upon receiving specific temporary HTTP errors. It will use the +// context associated with the HTTP request object. func RetryHTTPDo(client *http.Client, req *http.Request, b backoff.BackOff) (*http.Response, error) { var resp *http.Response op := func() error { @@ -64,6 +87,6 @@ func RetryHTTPDo(client *http.Client, req *http.Request, b backoff.BackOff) (*ht return err } - err := Retry(op, b) + err := Retry(op, backoff.WithContext(b, req.Context())) return resp, err }