Skip to content

Commit 477f370

Browse files
authored
Fix ratelimits (#28)
1 parent 1e17613 commit 477f370

File tree

2 files changed

+34
-68
lines changed

2 files changed

+34
-68
lines changed

client.go

Lines changed: 7 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
package aapi
22

33
import (
4-
"context"
54
"encoding/json"
65
"fmt"
76
"io"
87
"io/ioutil"
9-
"log"
10-
"math/rand"
118
"net/http"
129
"net/url"
1310
"sort"
14-
"strconv"
1511
"strings"
16-
"time"
1712

1813
"github.com/google/go-querystring/query"
1914
"github.com/hashicorp/go-retryablehttp"
20-
"golang.org/x/time/rate"
2115
)
2216

2317
const (
@@ -37,13 +31,11 @@ type PaginatedResponse struct {
3731

3832
type Client struct {
3933
// HTTP client used to communicate with the API.
40-
client *retryablehttp.Client
41-
token string
42-
baseURL *url.URL
43-
grafanaURL *url.URL
44-
disableRetries bool
45-
limiter *rate.Limiter
46-
UserAgent string
34+
client *retryablehttp.Client
35+
token string
36+
baseURL *url.URL
37+
grafanaURL *url.URL
38+
UserAgent string
4739
// List of Services. Keep in sync with func newClient
4840
Alerts *AlertService
4941
Integrations *IntegrationService
@@ -87,18 +79,8 @@ func New(base_url, token string) (*Client, error) {
8779
func newClient(url, grafana_url string) (*Client, error) {
8880
c := &Client{}
8981

90-
// Configure the HTTP client.
91-
c.client = &retryablehttp.Client{
92-
Backoff: c.retryHTTPBackoff,
93-
CheckRetry: c.retryHTTPCheck,
94-
RetryWaitMin: 100 * time.Millisecond,
95-
RetryWaitMax: 400 * time.Millisecond,
96-
RetryMax: 5,
97-
}
98-
// https://grafana.com/docs/grafana-cloud/oncall/oncall-api-reference/#rate-limits
99-
baseLimit := 50.0 / 60
100-
limit := rate.Limit(baseLimit)
101-
c.limiter = rate.NewLimiter(limit, 50)
82+
// retryablehttp.Client will retry up to 4 times on recoverable errors (429, 5xx, and low-level network errors)
83+
c.client = retryablehttp.NewClient()
10284

10385
// Set the default base URL. _ suppress error handling
10486
err := c.setBaseURL(url)
@@ -214,12 +196,6 @@ func (c *Client) NewRequest(method, path string, opt interface{}) (*retryablehtt
214196
// JSON decoded and stored in the value pointed to by v, or returned as an
215197
// error if an API error has occurred.
216198
func (c *Client) Do(req *retryablehttp.Request, v interface{}) (*http.Response, error) {
217-
err := c.limiter.Wait(req.Context())
218-
if err != nil {
219-
log.Println("limiter")
220-
return nil, err
221-
}
222-
223199
resp, err := c.client.Do(req)
224200
if err != nil {
225201
return nil, err
@@ -307,43 +283,6 @@ func (e *ErrorResponse) Error() string {
307283
return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message)
308284
}
309285

310-
func (c *Client) retryHTTPCheck(ctx context.Context, resp *http.Response, err error) (bool, error) {
311-
if ctx.Err() != nil {
312-
return false, ctx.Err()
313-
}
314-
if err != nil {
315-
return false, err
316-
}
317-
if !c.disableRetries && (resp.StatusCode == 429 || resp.StatusCode >= 500) {
318-
return true, nil
319-
}
320-
return false, nil
321-
}
322-
323-
func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
324-
if resp != nil && resp.StatusCode == 429 {
325-
return rateLimitBackoff(min, max, attemptNum, resp)
326-
}
327-
328-
return retryablehttp.LinearJitterBackoff(min, max, attemptNum, resp)
329-
}
330-
331-
func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
332-
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
333-
jitter := time.Duration(rnd.Float64() * float64(max-min))
334-
log.Printf("[DEBUG] ratelimited call")
335-
if resp != nil {
336-
if v := resp.Header.Get("RateLimit-Reset"); v != "" {
337-
if reset, _ := strconv.ParseInt(v, 10, 64); reset > 0 {
338-
log.Printf("[DEBUG] reset in '%d", reset)
339-
min = time.Duration(reset) * time.Second
340-
}
341-
}
342-
}
343-
344-
return min + jitter
345-
}
346-
347286
func (c *Client) BaseURL() *url.URL {
348287
u := *c.baseURL
349288
return &u

client_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,30 @@ func TestCheckResponse(t *testing.T) {
157157
t.Errorf("Expected error: %s, got %s", want, errResp.Error())
158158
}
159159
}
160+
161+
func TestRatelimitRetry(t *testing.T) {
162+
mux, server, client := setup(t)
163+
defer teardown(server)
164+
165+
requestCount := 0
166+
mux.HandleFunc("/api/v1/test", func(w http.ResponseWriter, r *http.Request) {
167+
requestCount++
168+
w.Header().Set("Retry-After", "0")
169+
w.WriteHeader(http.StatusTooManyRequests)
170+
})
171+
172+
req, err := client.NewRequest("GET", "test", nil)
173+
if err != nil {
174+
t.Fatalf("Failed to create request: %v", err)
175+
}
176+
177+
_, err = client.Do(req, nil)
178+
expectedErr := fmt.Sprintf("GET %s giving up after 5 attempt(s)", req.URL)
179+
if err.Error() != expectedErr {
180+
t.Fatalf("Expected error: %s, got %s", expectedErr, err.Error())
181+
}
182+
183+
if requestCount != 5 {
184+
t.Errorf("Expected 5 requests (1 original + 4 retries), got %d", requestCount)
185+
}
186+
}

0 commit comments

Comments
 (0)