Skip to content

Commit 49e39ab

Browse files
authored
Added body close on retry for downloader round trip (#10008)
Add missing body close method when webseed roundtrip is retried
1 parent 2b8f669 commit 49e39ab

File tree

1 file changed

+56
-8
lines changed

1 file changed

+56
-8
lines changed

erigon-lib/downloader/downloader.go

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"encoding/json"
2525
"errors"
2626
"fmt"
27+
"math"
2728
"math/rand"
2829
"net/http"
2930
"net/url"
@@ -33,6 +34,7 @@ import (
3334
"reflect"
3435
"runtime"
3536
"sort"
37+
"strconv"
3638
"strings"
3739
"sync"
3840
"sync/atomic"
@@ -148,15 +150,52 @@ func insertCloudflareHeaders(req *http.Request) {
148150
}
149151
}
150152

153+
// retryBackoff performs exponential backoff based on the attempt number and limited
154+
// by the provided minimum and maximum durations.
155+
//
156+
// It also tries to parse Retry-After response header when a http.StatusTooManyRequests
157+
// (HTTP Code 429) is found in the resp parameter. Hence it will return the number of
158+
// seconds the server states it may be ready to process more requests from this client.
159+
func calcBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
160+
if resp != nil {
161+
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable {
162+
if s, ok := resp.Header["Retry-After"]; ok {
163+
if sleep, err := strconv.ParseInt(s[0], 10, 64); err == nil {
164+
return time.Second * time.Duration(sleep)
165+
}
166+
}
167+
}
168+
}
169+
170+
mult := math.Pow(2, float64(attemptNum)) * float64(min)
171+
sleep := time.Duration(mult)
172+
if float64(sleep) != mult || sleep > max {
173+
sleep = max
174+
}
175+
176+
return sleep
177+
}
178+
151179
func (r *requestHandler) RoundTrip(req *http.Request) (resp *http.Response, err error) {
180+
defer func() {
181+
if r := recover(); r != nil {
182+
if resp != nil && resp.Body != nil {
183+
resp.Body.Close()
184+
resp.Body = nil
185+
}
186+
187+
err = fmt.Errorf("http client panic: %s", r)
188+
}
189+
}()
190+
152191
insertCloudflareHeaders(req)
153192

154193
resp, err = r.Transport.RoundTrip(req)
155194

156-
delay := 500 * time.Millisecond
157195
attempts := 1
158196
retry := true
159197

198+
const minDelay = 500 * time.Millisecond
160199
const maxDelay = 5 * time.Second
161200
const maxAttempts = 10
162201

@@ -180,26 +219,35 @@ func (r *requestHandler) RoundTrip(req *http.Request) (resp *http.Response, err
180219
r.downloader.stats.WebseedBytesDownload.Add(resp.ContentLength)
181220
retry = false
182221

183-
case http.StatusInternalServerError, http.StatusBadGateway:
222+
// the first two statuses here have been observed from cloudflare
223+
// during testing. The remainder are generally understood to be
224+
// retriable http responses, calcBackoff will use the Retry-After
225+
// header if its availible
226+
case http.StatusInternalServerError, http.StatusBadGateway,
227+
http.StatusRequestTimeout, http.StatusTooEarly,
228+
http.StatusTooManyRequests, http.StatusServiceUnavailable,
229+
http.StatusGatewayTimeout:
230+
184231
r.downloader.stats.WebseedServerFails.Add(1)
185232

233+
if resp.Body != nil {
234+
resp.Body.Close()
235+
resp.Body = nil
236+
}
237+
186238
attempts++
187-
delayTimer := time.NewTimer(delay)
239+
delayTimer := time.NewTimer(calcBackoff(minDelay, maxDelay, attempts, resp))
188240

189241
select {
190242
case <-delayTimer.C:
191243
// Note this assumes the req.Body is nil
192244
resp, err = r.Transport.RoundTrip(req)
193245
r.downloader.stats.WebseedTripCount.Add(1)
194246

195-
if err == nil && delay < maxDelay {
196-
delay = delay + (time.Duration(rand.Intn(200-75)+75)*delay)/100
197-
}
198-
199247
case <-req.Context().Done():
200248
err = req.Context().Err()
201249
}
202-
retry = attempts > maxAttempts
250+
retry = attempts < maxAttempts
203251

204252
default:
205253
r.downloader.stats.WebseedBytesDownload.Add(resp.ContentLength)

0 commit comments

Comments
 (0)