소스 검색

Slight better rate limit handling

This improves greatly on the previous rate limit handling
however still needs review and possible improvement.
Please report bugs!
Bruce Marriner 9 년 전
부모
커밋
a24f9e3d10
2개의 변경된 파일43개의 추가작업 그리고 1개의 파일을 삭제
  1. 32 1
      restapi.go
  2. 11 0
      structs.go

+ 32 - 1
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

+ 11 - 0
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.