Skip to content
This repository was archived by the owner on Dec 10, 2024. It is now read-only.

Commit 6404ea3

Browse files
authored
Merge pull request #1993 from RicePatrick/add-retry-exponential-backoff
Add an exponential backoff to the retry function
2 parents 203df8e + cd5f603 commit 6404ea3

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

gitlab.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"errors"
2525
"fmt"
2626
"io"
27+
"math"
2728
"math/rand"
2829
"mime/multipart"
2930
"net/http"
@@ -510,7 +511,7 @@ func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp *
510511
// min and max are mainly used for bounding the jitter that will be added to
511512
// the reset time retrieved from the headers. But if the final wait time is
512513
// less then min, min will be used instead.
513-
func rateLimitBackoff(min, max time.Duration, _ int, resp *http.Response) time.Duration {
514+
func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
514515
// rnd is used to generate pseudo-random numbers.
515516
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
516517

@@ -525,6 +526,11 @@ func rateLimitBackoff(min, max time.Duration, _ int, resp *http.Response) time.D
525526
min = wait
526527
}
527528
}
529+
} else {
530+
// In case the RateLimit-Reset header is not set, back off an additional
531+
// 100% exponentially. With the default milliseconds being set to 100 for
532+
// `min`, this makes the 5th retry wait 3.2 seconds (3,200 ms) by default.
533+
min = time.Duration(float64(min) * math.Pow(2, float64(attemptNum)))
528534
}
529535
}
530536

gitlab_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,3 +418,39 @@ func TestPaginationPopulatePageValuesKeyset(t *testing.T) {
418418
}
419419
}
420420
}
421+
422+
func TestExponentialBackoffLogic(t *testing.T) {
423+
// Can't use the default `setup` because it disabled the backoff
424+
mux := http.NewServeMux()
425+
server := httptest.NewServer(mux)
426+
t.Cleanup(server.Close)
427+
client, err := NewClient("",
428+
WithBaseURL(server.URL),
429+
)
430+
if err != nil {
431+
t.Fatalf("Failed to create client: %v", err)
432+
}
433+
434+
// Create a method that returns 429
435+
mux.HandleFunc("/api/v4/projects/1", func(w http.ResponseWriter, r *http.Request) {
436+
testMethod(t, r, http.MethodGet)
437+
w.WriteHeader(http.StatusTooManyRequests)
438+
})
439+
440+
// Measure the time at the start of the test
441+
start := time.Now()
442+
443+
// Send a request (which will get a bunch of 429s)
444+
// None of the responses matter, so ignore them all
445+
_, resp, _ := client.Projects.GetProject(1, nil)
446+
end := time.Now()
447+
448+
// The test should run for _at least_ 3,200 milliseconds
449+
duration := float64(end.Sub(start))
450+
if duration < float64(3200*time.Millisecond) {
451+
t.Fatal("Wait was shorter than expected. Expected a minimum of 5 retries taking 3200 milliseconds, got:", duration)
452+
}
453+
if resp.StatusCode != 429 {
454+
t.Fatal("Expected to get a 429 code given the server is hard-coded to return this. Received instead:", resp.StatusCode)
455+
}
456+
}

0 commit comments

Comments
 (0)