Skip to content

Commit 7dadac6

Browse files
committed
Fix handling of rate limit by checking response headers in transport
Signed-off-by: Lou Marvin Caraig <[email protected]>
1 parent 4c73faf commit 7dadac6

File tree

1 file changed

+21
-49
lines changed

1 file changed

+21
-49
lines changed

utils/rate.go

Lines changed: 21 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package utils
22

33
import (
4-
"bytes"
5-
"fmt"
6-
"io"
7-
"io/ioutil"
84
"log"
95
"net/http"
6+
"strconv"
107
"sync"
118
"time"
129

@@ -55,45 +52,37 @@ func (rlt *rateLimitTransport) RoundTrip(req *http.Request) (*http.Response, err
5552
return resp, err
5653
}
5754

58-
// Make response body accessible for retries & debugging
59-
// (work around bug in GitHub SDK)
60-
// See https://github.com/google/go-github/pull/986
61-
r1, r2, err := drainBody(resp.Body)
62-
if err != nil {
63-
return nil, err
64-
}
65-
resp.Body = r1
66-
ghErr := github.CheckResponse(resp)
67-
resp.Body = r2
68-
69-
// When you have been limited, use the Retry-After response header to slow down.
70-
if arlErr, ok := ghErr.(*github.AbuseRateLimitError); ok {
55+
if resp.Header.Get("X-RateLimit-Remaining") == "0" {
7156
rlt.delayNextRequest = false
72-
retryAfter := arlErr.GetRetryAfter()
73-
log.Printf("[DEBUG] Abuse detection mechanism triggered, sleeping for %s before retrying",
74-
retryAfter)
75-
time.Sleep(retryAfter)
76-
rlt.unlock(req)
77-
return rlt.RoundTrip(req)
78-
}
7957

80-
if rlErr, ok := ghErr.(*github.RateLimitError); ok {
81-
rlt.delayNextRequest = false
82-
retryAfter := rlErr.Rate.Reset.Sub(time.Now())
58+
var limit int
59+
if limitHeader := resp.Header.Get("X-RateLimit-Limit"); limitHeader != "" {
60+
limit, _ = strconv.Atoi(limitHeader)
61+
}
8362

84-
if retryAfter < 0 {
85-
fmt.Println("what!", rlErr.Rate.Reset, time.Now())
63+
var reset github.Timestamp
64+
if resetHeader := resp.Header.Get("X-RateLimit-Reset"); resetHeader != "" {
65+
if v, _ := strconv.ParseInt(resetHeader, 10, 64); v != 0 {
66+
reset = github.Timestamp{time.Unix(v, 0)}
67+
}
8668
}
8769

70+
retryAfter := reset.Sub(time.Now())
71+
8872
log.Printf("[DEBUG] Rate limit %d reached, sleeping for %s before retrying",
89-
rlErr.Rate.Limit, retryAfter)
90-
time.Sleep(retryAfter)
73+
limit, retryAfter)
74+
if retryAfter < 0 {
75+
log.Printf("[WARN] retryAfter < 0. reset: %v | now: %v",
76+
reset, time.Now())
77+
} else {
78+
time.Sleep(retryAfter)
79+
}
80+
9181
rlt.unlock(req)
9282
return rlt.RoundTrip(req)
9383
}
9484

9585
rlt.unlock(req)
96-
9786
return resp, nil
9887
}
9988

@@ -105,23 +94,6 @@ func (rlt *rateLimitTransport) unlock(req *http.Request) {
10594
rlt.m.Unlock()
10695
}
10796

108-
// drainBody reads all of b to memory and then returns two equivalent
109-
// ReadClosers yielding the same bytes.
110-
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
111-
if b == http.NoBody {
112-
// No copying needed. Preserve the magic sentinel meaning of NoBody.
113-
return http.NoBody, http.NoBody, nil
114-
}
115-
var buf bytes.Buffer
116-
if _, err = buf.ReadFrom(b); err != nil {
117-
return nil, b, err
118-
}
119-
if err = b.Close(); err != nil {
120-
return nil, b, err
121-
}
122-
return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
123-
}
124-
12597
func isWriteMethod(method string) bool {
12698
switch method {
12799
case "POST", "PATCH", "PUT", "DELETE":

0 commit comments

Comments
 (0)