Skip to content

Commit 854e727

Browse files
authored
use github.com/cenkalti/backoff/v5 for apiclient backoff (#3662)
* cscli: handle sigint/sigterm, cancel context of ongoing http req * use github.com/cenkalti/backoff/v5 for apiclient backoff * unexport method * lint * comment
1 parent e542890 commit 854e727

File tree

3 files changed

+48
-37
lines changed

3 files changed

+48
-37
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
github.com/blackfireio/osinfo v1.0.5
1717
github.com/bluele/gcache v0.0.2
1818
github.com/buger/jsonparser v1.1.1
19+
github.com/cenkalti/backoff/v5 v5.0.2
1920
github.com/cespare/xxhash/v2 v2.3.0
2021
github.com/corazawaf/coraza/v3 v3.3.3
2122
github.com/corazawaf/libinjection-go v0.2.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iC
8181
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
8282
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
8383
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
84+
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
85+
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
8486
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
8587
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
8688
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=

pkg/apiclient/auth_retry.go

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,84 @@
11
package apiclient
22

33
import (
4-
"math/rand"
4+
"fmt"
55
"net/http"
66
"slices"
77
"time"
88

9+
"github.com/cenkalti/backoff/v5"
910
log "github.com/sirupsen/logrus"
1011

1112
"github.com/crowdsecurity/crowdsec/pkg/fflag"
1213
)
1314

1415
type retryRoundTripper struct {
1516
next http.RoundTripper
16-
maxAttempts int
17+
maxAttempts uint
1718
retryStatusCodes []int
1819
withBackOff bool
19-
onBeforeRequest func(attempt int)
2020
}
2121

22-
func (r retryRoundTripper) ShouldRetry(statusCode int) bool {
22+
func (r retryRoundTripper) shouldRetry(statusCode int) bool {
2323
return slices.Contains(r.retryStatusCodes, statusCode)
2424
}
2525

2626
func (r retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
27-
var (
28-
resp *http.Response
29-
err error
30-
)
31-
32-
backoff := 0
33-
maxAttempts := r.maxAttempts
34-
27+
maxAttempts := max(r.maxAttempts, 1)
3528
if fflag.DisableHttpRetryBackoff.IsEnabled() {
3629
maxAttempts = 1
3730
}
3831

39-
for i := range maxAttempts {
40-
if i > 0 {
41-
if r.withBackOff {
42-
//nolint:gosec
43-
backoff += 10 + rand.Intn(20)
44-
}
45-
46-
log.Infof("retrying in %d seconds (attempt %d of %d)", backoff, i+1, r.maxAttempts)
47-
48-
select {
49-
case <-req.Context().Done():
50-
return nil, req.Context().Err()
51-
case <-time.After(time.Duration(backoff) * time.Second):
52-
}
53-
}
32+
var bo backoff.BackOff
33+
34+
if r.withBackOff {
35+
// Use exponential + jitter; the default values are:
36+
//
37+
// DefaultInitialInterval = 500 * time.Millisecond
38+
// DefaultRandomizationFactor = 0.5
39+
// DefaultMultiplier = 1.5
40+
// DefaultMaxInterval = 60 * time.Second
41+
// MaxElapsedTime = 15 * time.Minute
42+
exp := backoff.NewExponentialBackOff()
43+
exp.InitialInterval = 20 * time.Second
44+
exp.Multiplier = 2
45+
bo = exp
46+
} else {
47+
// backoff is disabled, policy of "no wait"
48+
bo = backoff.NewConstantBackOff(0)
49+
}
5450

55-
if r.onBeforeRequest != nil {
56-
r.onBeforeRequest(i)
57-
}
51+
attemptLeft := maxAttempts
5852

53+
operation := func() (*http.Response, error) {
5954
clonedReq := cloneRequest(req)
6055

61-
resp, err = r.next.RoundTrip(clonedReq)
56+
attemptLeft--
57+
58+
resp, err := r.next.RoundTrip(clonedReq)
6259
if err != nil {
63-
if left := maxAttempts - i - 1; left > 0 {
64-
log.Errorf("error while performing request: %s; %d retries left", err, left)
60+
if attemptLeft > 0 {
61+
log.Errorf("while performing request: %s; %d retries left", err, attemptLeft)
6562
}
6663

67-
continue
64+
return nil, fmt.Errorf("retryable error: %w", err)
6865
}
6966

70-
if !r.ShouldRetry(resp.StatusCode) {
71-
return resp, nil
67+
if r.shouldRetry(resp.StatusCode) {
68+
log.Errorf("request returned status %d: %s; %d retries left", resp.StatusCode, resp.Status, attemptLeft)
69+
return nil, fmt.Errorf("retryable status: %d", resp.StatusCode)
7270
}
71+
72+
return resp, nil
73+
}
74+
75+
resp, err := backoff.Retry(req.Context(), operation,
76+
backoff.WithBackOff(bo),
77+
backoff.WithMaxTries(maxAttempts),
78+
)
79+
if err != nil {
80+
return nil, err
7381
}
7482

75-
return resp, err
83+
return resp, nil
7684
}

0 commit comments

Comments
 (0)