Slight better rate limit handling

This improves greatly on the previous rate limit handling
however still needs review and possible improvement.
Please report bugs!
This commit is contained in:
Bruce Marriner 2016-04-28 13:38:20 -05:00
parent dd69a7e27f
commit a24f9e3d10
2 changed files with 43 additions and 1 deletions

View file

@ -25,6 +25,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"sync"
"time" "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. // request makes a (GET/POST/...) Requests to Discord REST API.
func (s *Session) request(method, urlStr, contentType string, b []byte) (response []byte, err error) { 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 { if s.Debug {
log.Printf("API REQUEST %8s :: %s\n", method, urlStr) log.Printf("API REQUEST %8s :: %s\n", method, urlStr)
log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b)) 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)} client := &http.Client{Timeout: (20 * time.Second)}
mu.Lock()
resp, err := client.Do(req) resp, err := client.Do(req)
mu.Unlock()
if err != nil { if err != nil {
return 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. // TODO check for 401 response, invalidate token if we get one.
case 429: // TOO MANY REQUESTS - Rate limiting case 429: // TOO MANY REQUESTS - Rate limiting
rl := RateLimit{} rl := RateLimit{}
err = json.Unmarshal(response, &rl) err = json.Unmarshal(response, &rl)
if err != nil { if err != nil {
err = fmt.Errorf("Request unmarshal rate limit error : %+v", err) s.log(LogError, "rate limit unmarshal error, %s", err)
return return
} }
s.log(LogInformational, "Rate Limiting %s, retry in %d", urlStr, rl.RetryAfter)
mu.Lock()
time.Sleep(rl.RetryAfter) 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) response, err = s.request(method, urlStr, contentType, b)
default: // Error condition default: // Error condition

View file

@ -75,6 +75,17 @@ type Session struct {
// When nil, the session is not listening. // When nil, the session is not listening.
listening chan interface{} 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. // A VoiceRegion stores data for a specific voice region server.