From a24f9e3d108e04b93d8a08b39d54f0ec5c5695b8 Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Thu, 28 Apr 2016 13:38:20 -0500 Subject: [PATCH] Slight better rate limit handling This improves greatly on the previous rate limit handling however still needs review and possible improvement. Please report bugs! --- restapi.go | 33 ++++++++++++++++++++++++++++++++- structs.go | 11 +++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/restapi.go b/restapi.go index f740b42..99d3533 100644 --- a/restapi.go +++ b/restapi.go @@ -25,6 +25,8 @@ import ( "net/http" "net/url" "strconv" + "strings" + "sync" "time" ) @@ -49,6 +51,26 @@ func (s *Session) Request(method, urlStr string, data interface{}) (response []b // request makes a (GET/POST/...) Requests to Discord REST API. func (s *Session) request(method, urlStr, contentType string, b []byte) (response []byte, err error) { + // rate limit mutex for this url + // TODO: review for performance improvements + // ideally we just ignore endpoints that we've never + // received a 429 on. But this simple method works and + // is a lot less complex :) It also might even be more + // performat due to less checks and maps. + var mu *sync.Mutex + s.rateLimit.Lock() + if s.rateLimit.url == nil { + s.rateLimit.url = make(map[string]*sync.Mutex) + } + + bu := strings.Split(urlStr, "?") + mu, _ = s.rateLimit.url[bu[0]] + if mu == nil { + mu = new(sync.Mutex) + s.rateLimit.url[urlStr] = mu + } + s.rateLimit.Unlock() + if s.Debug { log.Printf("API REQUEST %8s :: %s\n", method, urlStr) log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b)) @@ -77,7 +99,9 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons client := &http.Client{Timeout: (20 * time.Second)} + mu.Lock() resp, err := client.Do(req) + mu.Unlock() if err != nil { return } @@ -111,13 +135,20 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons // TODO check for 401 response, invalidate token if we get one. case 429: // TOO MANY REQUESTS - Rate limiting + rl := RateLimit{} err = json.Unmarshal(response, &rl) if err != nil { - err = fmt.Errorf("Request unmarshal rate limit error : %+v", err) + s.log(LogError, "rate limit unmarshal error, %s", err) return } + s.log(LogInformational, "Rate Limiting %s, retry in %d", urlStr, rl.RetryAfter) + mu.Lock() time.Sleep(rl.RetryAfter) + mu.Unlock() + // we can make the above smarter + // this method can cause longer delays then required + response, err = s.request(method, urlStr, contentType, b) default: // Error condition diff --git a/structs.go b/structs.go index 18e24f5..94995bb 100644 --- a/structs.go +++ b/structs.go @@ -75,6 +75,17 @@ type Session struct { // When nil, the session is not listening. listening chan interface{} + + // used to deal with rate limits + // may switch to slices later + // TODO: performance test map vs slices + rateLimit rateLimitMutex +} + +type rateLimitMutex struct { + sync.Mutex + url map[string]*sync.Mutex + bucket map[string]*sync.Mutex // TODO :) } // A VoiceRegion stores data for a specific voice region server.