Skip to content

Commit d0d0b8f

Browse files
committed
feat(agent): randomize retry-after interval when header is undefined
This commit enhances the client’s retry logic by replacing a fixed fallback wait time with a randomized interval whenever the server does not supply a Retry-After header. Instead of always pausing for 5 seconds, the code now computes a random duration between 5 seconds and 65 seconds. This change helps distribute retry attempts more evenly across clients, mitigating the risk of simultaneous reconnections (the “thundering herd” problem) when a service becomes available again.
1 parent f8af39d commit d0d0b8f

File tree

1 file changed

+19
-5
lines changed

1 file changed

+19
-5
lines changed

pkg/api/client/client.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"math"
8+
"math/rand/v2"
89
"net"
910
"net/http"
1011
"net/url"
@@ -61,8 +62,21 @@ func NewClient(address string, opts ...Opt) (Client, error) {
6162

6263
// DefaultMaxRetryWaitTime is the default value for wait time between retries.
6364
const DefaultMaxRetryWaitTime time.Duration = 1 * time.Hour
64-
// DefaultRetryAfterTime is the retry default time when the header [RetryAfterHeader] isn't defined on the response.
65-
const DefaultRetryAfterTime time.Duration = 5 * time.Second
65+
66+
randomWaitTimeSecs := func() time.Duration {
67+
const DefaultMinRetryAfterSecs int = 5
68+
const DefaultMaxRetryAfterSecs int = 65
69+
70+
t := time.Duration(rand.IntN(DefaultMaxRetryAfterSecs-DefaultMinRetryAfterSecs)+DefaultMinRetryAfterSecs) * time.Second //nolint:gosec
71+
72+
log.WithFields(log.Fields{
73+
"min": DefaultMinRetryAfterSecs,
74+
"max": DefaultMaxRetryAfterSecs,
75+
"retry_after": t,
76+
}).Debug("retrying request after a random time period")
77+
78+
return t
79+
}
6680

6781
client := new(client)
6882
client.http = resty.New()
@@ -104,15 +118,15 @@ func NewClient(address string, opts ...Opt) (Client, error) {
104118
case http.StatusTooManyRequests, http.StatusServiceUnavailable:
105119
retryAfterHeader := r.Header().Get(RetryAfterHeader)
106120
if retryAfterHeader == "" {
107-
return DefaultRetryAfterTime, nil
121+
return randomWaitTimeSecs(), nil
108122
}
109123

110124
// NOTE: The `Retry-After` supports delay in seconds and and a date time, but currently we will support only
111125
// one of them.
112126
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
113127
retryAfterSeconds, err := strconv.Atoi(retryAfterHeader)
114128
if err != nil {
115-
return DefaultRetryAfterTime, err
129+
return randomWaitTimeSecs(), err
116130
}
117131

118132
log.WithFields(log.Fields{
@@ -123,7 +137,7 @@ func NewClient(address string, opts ...Opt) (Client, error) {
123137

124138
return time.Duration(retryAfterSeconds) * time.Second, nil
125139
default:
126-
return DefaultRetryAfterTime, nil
140+
return randomWaitTimeSecs(), nil
127141
}
128142
})
129143
client.http.SetRetryMaxWaitTime(DefaultMaxRetryWaitTime)

0 commit comments

Comments
 (0)