Repository: go-resty/resty (go)

Issue:
Do not wait retry time if exceeded max attempts
In this block https://github.com/go-resty/resty/blob/70336cb87f39407ce7f8d41ffcd2b40b9729f401/retry.go#L104-L145
when `attempt == opts.maxAttempts` and `onRetryCondition` returned true we wait extra time for retry. But after waiting just return err as long as we don't enter cycle anymore.

Example: https://play.golang.org/p/v3l6LmcWRvA. Should wait 2 seconds for 1 retry instead of 4.

Pull request:
Don't wait after last retry
Add check not to wait after last retry. Did it after retry hooks to keep compatibility.

Closes #443 

Signed-off-by: Pavel <kositsyn.pa@phystech.edu>

Patch:
diff --git a/retry.go b/retry.go
index a841c46..00b8514 100644
--- a/retry.go
+++ b/retry.go
@@ -129,6 +129,12 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
 			hook(resp, err)
 		}
 
+		// Don't need to wait when no retries left.
+		// Still run retry hooks even on last retry to keep compatibility.
+		if attempt == opts.maxRetries {
+			return err
+		}
+
 		waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
 		if err2 != nil {
 			if err == nil {


Test patch:
diff --git a/retry_test.go b/retry_test.go
index e302216..9f8fb38 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -32,6 +32,39 @@ func TestBackoffSuccess(t *testing.T) {
 	assertEqual(t, externalCounter, attempts)
 }
 
+func TestBackoffNoWaitForLastRetry(t *testing.T) {
+	attempts := 1
+	externalCounter := 0
+	numRetries := 1
+
+	canceledCtx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	resp := &Response{
+		Request: &Request{
+			ctx: canceledCtx,
+			client: &Client{
+				RetryAfter: func(*Client, *Response) (time.Duration, error) {
+					return 6, nil
+				},
+			},
+		},
+	}
+
+	retryErr := Backoff(func() (*Response, error) {
+		externalCounter++
+		return resp, nil
+	}, RetryConditions([]RetryConditionFunc{func(response *Response, err error) bool {
+		if externalCounter == attempts + numRetries {
+			// Backoff returns context canceled if goes to sleep after last retry.
+			cancel()
+		}
+		return true
+	}}), Retries(numRetries))
+
+	assertNil(t, retryErr)
+}
+
 func TestBackoffTenAttemptsSuccess(t *testing.T) {
 	attempts := 10
 	externalCounter := 0
@@ -169,8 +202,8 @@ func TestClientRetryGet(t *testing.T) {
 	assertNotNil(t, resp.Body())
 	assertEqual(t, 0, len(resp.Header()))
 
-	assertEqual(t, true, (strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
-		strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\"")))
+	assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
+		strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
 }
 
 func TestClientRetryWait(t *testing.T) {
@@ -639,8 +672,8 @@ func TestClientRetryCount(t *testing.T) {
 	// 2 attempts were made
 	assertEqual(t, attempt, 2)
 
-	assertEqual(t, true, (strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
-		strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\"")))
+	assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
+		strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
 }
 
 func TestClientErrorRetry(t *testing.T) {
@@ -693,8 +726,8 @@ func TestClientRetryHook(t *testing.T) {
 
 	assertEqual(t, 3, attempt)
 
-	assertEqual(t, true, (strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
-		strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\"")))
+	assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
+		strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
 }
 
 func filler(*Response, error) bool {