Skip to content

Commit e1f5485

Browse files
authored
Add a new RateLimitLinearJitterBackoff policy
1 parent b0cac1e commit e1f5485

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

client.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,23 @@ func LinearJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Resp
638638
return time.Duration(jitterMin * int64(attemptNum))
639639
}
640640

641+
// RateLimitLinearJitterBackoff wraps the retryablehttp.LinearJitterBackoff.
642+
// It first checks if the response status code is http.StatusTooManyRequests
643+
// (HTTP Code 429) or http.StatusServiceUnavailable (HTTP Code 503). If it is
644+
// and the response contains a Retry-After response header, it will wait the
645+
// amount of time specified by the header. Otherwise, this calls
646+
// LinearJitterBackoff.
647+
func RateLimitLinearJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
648+
if resp != nil {
649+
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable {
650+
if sleep, ok := parseRetryAfterHeader(resp.Header["Retry-After"]); ok {
651+
return sleep
652+
}
653+
}
654+
}
655+
return LinearJitterBackoff(min, max, attemptNum, resp)
656+
}
657+
641658
// PassthroughErrorHandler is an ErrorHandler that directly passes through the
642659
// values from the net/http library for the final request. The body is not
643660
// closed.

client_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,113 @@ func TestClient_PostForm(t *testing.T) {
11431143
resp.Body.Close()
11441144
}
11451145

1146+
func TestBackoff_RateLimitLinearJitterBackoff(t *testing.T) {
1147+
testCases := []struct {
1148+
name string
1149+
min time.Duration
1150+
max time.Duration
1151+
headers http.Header
1152+
responseCode int
1153+
expect time.Duration
1154+
}{
1155+
{
1156+
name: "429 no retry header",
1157+
min: time.Second,
1158+
max: time.Second,
1159+
headers: http.Header{},
1160+
responseCode: http.StatusTooManyRequests,
1161+
expect: time.Second,
1162+
},
1163+
{
1164+
name: "503 no retry header",
1165+
min: time.Second,
1166+
max: time.Second,
1167+
headers: http.Header{},
1168+
responseCode: http.StatusServiceUnavailable,
1169+
expect: time.Second,
1170+
},
1171+
{
1172+
name: "429 retry header",
1173+
min: time.Second,
1174+
max: time.Second,
1175+
headers: http.Header{
1176+
"Retry-After": []string{"2"},
1177+
},
1178+
responseCode: http.StatusTooManyRequests,
1179+
expect: 2 * time.Second,
1180+
},
1181+
{
1182+
name: "503 retry header",
1183+
min: time.Second,
1184+
max: time.Second,
1185+
headers: http.Header{
1186+
"Retry-After": []string{"2"},
1187+
},
1188+
responseCode: http.StatusServiceUnavailable,
1189+
expect: 2 * time.Second,
1190+
},
1191+
{
1192+
name: "502 ignore retry header",
1193+
min: time.Second,
1194+
max: time.Second,
1195+
headers: http.Header{
1196+
"Retry-After": []string{"2"},
1197+
},
1198+
responseCode: http.StatusBadGateway,
1199+
expect: time.Second,
1200+
},
1201+
{
1202+
name: "502 no retry header",
1203+
min: time.Second,
1204+
max: time.Second,
1205+
headers: http.Header{},
1206+
responseCode: http.StatusBadGateway,
1207+
expect: time.Second,
1208+
},
1209+
{
1210+
name: "429 retry header with jitter",
1211+
min: time.Second,
1212+
max: 5 * time.Second,
1213+
headers: http.Header{
1214+
"Retry-After": []string{"2"},
1215+
},
1216+
responseCode: http.StatusTooManyRequests,
1217+
expect: 2 * time.Second,
1218+
},
1219+
{
1220+
name: "429 retry header less than min",
1221+
min: 5 * time.Second,
1222+
max: 10 * time.Second,
1223+
headers: http.Header{
1224+
"Retry-After": []string{"2"},
1225+
},
1226+
responseCode: http.StatusTooManyRequests,
1227+
expect: 2 * time.Second,
1228+
},
1229+
{
1230+
name: "429 retry header in range",
1231+
min: time.Second,
1232+
max: 10 * time.Second,
1233+
headers: http.Header{
1234+
"Retry-After": []string{"2"},
1235+
},
1236+
responseCode: http.StatusTooManyRequests,
1237+
expect: 2 * time.Second,
1238+
},
1239+
}
1240+
for _, tc := range testCases {
1241+
t.Run(tc.name, func(t *testing.T) {
1242+
got := RateLimitLinearJitterBackoff(tc.min, tc.max, 0, &http.Response{
1243+
StatusCode: tc.responseCode,
1244+
Header: tc.headers,
1245+
})
1246+
if got != tc.expect {
1247+
t.Fatalf("expected %s, got %s", tc.expect, got)
1248+
}
1249+
})
1250+
}
1251+
}
1252+
11461253
func TestBackoff(t *testing.T) {
11471254
type tcase struct {
11481255
min time.Duration

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy